@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.js
CHANGED
|
@@ -280,6 +280,7 @@ var ServiceContext = class {
|
|
|
280
280
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
281
281
|
this._abortController = new AbortController();
|
|
282
282
|
this._invoke = config.invoke;
|
|
283
|
+
this._invokeAny = config.invokeAny;
|
|
283
284
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
284
285
|
this._hosts = config.hosts;
|
|
285
286
|
this._session = config.session ?? null;
|
|
@@ -324,6 +325,12 @@ var ServiceContext = class {
|
|
|
324
325
|
get invoke() {
|
|
325
326
|
return this._invoke;
|
|
326
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Get the multi-resource invoke function when available.
|
|
330
|
+
*/
|
|
331
|
+
get invokeAny() {
|
|
332
|
+
return this._invokeAny;
|
|
333
|
+
}
|
|
327
334
|
/**
|
|
328
335
|
* Get the fetch function for HTTP requests.
|
|
329
336
|
*/
|
|
@@ -1910,6 +1917,683 @@ var DuckDbService = class extends BaseService {
|
|
|
1910
1917
|
};
|
|
1911
1918
|
DuckDbService.serviceName = "duckdb";
|
|
1912
1919
|
|
|
1920
|
+
// src/hooks/HooksService.ts
|
|
1921
|
+
var AsyncQueue = class {
|
|
1922
|
+
constructor() {
|
|
1923
|
+
this.values = [];
|
|
1924
|
+
this.waiters = [];
|
|
1925
|
+
this.closed = false;
|
|
1926
|
+
}
|
|
1927
|
+
[Symbol.asyncIterator]() {
|
|
1928
|
+
return this;
|
|
1929
|
+
}
|
|
1930
|
+
push(value) {
|
|
1931
|
+
if (this.closed) {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const waiter = this.waiters.shift();
|
|
1935
|
+
if (waiter) {
|
|
1936
|
+
waiter({ value, done: false });
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
this.values.push(value);
|
|
1940
|
+
}
|
|
1941
|
+
close() {
|
|
1942
|
+
if (this.closed) {
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
this.closed = true;
|
|
1946
|
+
while (this.waiters.length > 0) {
|
|
1947
|
+
const waiter = this.waiters.shift();
|
|
1948
|
+
waiter?.({ value: void 0, done: true });
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
next() {
|
|
1952
|
+
if (this.values.length > 0) {
|
|
1953
|
+
const value = this.values.shift();
|
|
1954
|
+
return Promise.resolve({ value, done: false });
|
|
1955
|
+
}
|
|
1956
|
+
if (this.closed) {
|
|
1957
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
1958
|
+
}
|
|
1959
|
+
return new Promise((resolve) => {
|
|
1960
|
+
this.waiters.push(resolve);
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
var HooksService = class extends BaseService {
|
|
1965
|
+
constructor(config = {}) {
|
|
1966
|
+
super();
|
|
1967
|
+
this._subscribers = /* @__PURE__ */ new Set();
|
|
1968
|
+
this._refreshChain = Promise.resolve();
|
|
1969
|
+
this._activeSignature = "";
|
|
1970
|
+
this._config = config;
|
|
1971
|
+
}
|
|
1972
|
+
get config() {
|
|
1973
|
+
return this._config;
|
|
1974
|
+
}
|
|
1975
|
+
get host() {
|
|
1976
|
+
return this._config.host ?? this.context.hosts[0];
|
|
1977
|
+
}
|
|
1978
|
+
async *subscribe(subscriptions, options = {}) {
|
|
1979
|
+
if (!this.requireAuth()) {
|
|
1980
|
+
throw new Error("Authentication required for hooks subscription");
|
|
1981
|
+
}
|
|
1982
|
+
if (subscriptions.length === 0) {
|
|
1983
|
+
throw new Error("At least one hook subscription is required");
|
|
1984
|
+
}
|
|
1985
|
+
const normalized = subscriptions.map(normalizeSubscription);
|
|
1986
|
+
const subscriber = {
|
|
1987
|
+
requested: normalized,
|
|
1988
|
+
ttlSeconds: options.ttlSeconds,
|
|
1989
|
+
queue: new AsyncQueue()
|
|
1990
|
+
};
|
|
1991
|
+
this._subscribers.add(subscriber);
|
|
1992
|
+
const abortHandler = () => {
|
|
1993
|
+
this._subscribers.delete(subscriber);
|
|
1994
|
+
subscriber.queue.close();
|
|
1995
|
+
void this.scheduleSharedStreamRefresh();
|
|
1996
|
+
};
|
|
1997
|
+
if (options.signal) {
|
|
1998
|
+
if (options.signal.aborted) {
|
|
1999
|
+
abortHandler();
|
|
2000
|
+
} else {
|
|
2001
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
void this.scheduleSharedStreamRefresh();
|
|
2005
|
+
try {
|
|
2006
|
+
for await (const event of subscriber.queue) {
|
|
2007
|
+
yield event;
|
|
2008
|
+
}
|
|
2009
|
+
} finally {
|
|
2010
|
+
if (options.signal) {
|
|
2011
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
2012
|
+
}
|
|
2013
|
+
abortHandler();
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
async register(webhook) {
|
|
2017
|
+
if (!this.requireAuth()) {
|
|
2018
|
+
return err(authRequiredError("hooks"));
|
|
2019
|
+
}
|
|
2020
|
+
if (typeof webhook.secret !== "string" || webhook.secret.trim().length === 0) {
|
|
2021
|
+
return err(
|
|
2022
|
+
serviceError(
|
|
2023
|
+
ErrorCodes.INVALID_INPUT,
|
|
2024
|
+
"Webhook secret is required",
|
|
2025
|
+
"hooks",
|
|
2026
|
+
{ meta: { field: "secret" } }
|
|
2027
|
+
)
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
const response = await this.context.fetch(`${this.host}/hooks/webhooks`, {
|
|
2032
|
+
method: "POST",
|
|
2033
|
+
headers: {
|
|
2034
|
+
...serviceHeadersToRecord(
|
|
2035
|
+
this.createHookHeaders(
|
|
2036
|
+
"tinycloud.hooks/register",
|
|
2037
|
+
buildScopePath(webhook.service, webhook.pathPrefix)
|
|
2038
|
+
)
|
|
2039
|
+
),
|
|
2040
|
+
"content-type": "application/json"
|
|
2041
|
+
},
|
|
2042
|
+
body: JSON.stringify({
|
|
2043
|
+
space: webhook.space,
|
|
2044
|
+
service: webhook.service,
|
|
2045
|
+
pathPrefix: normalizePathPrefix(webhook.pathPrefix),
|
|
2046
|
+
abilities: webhook.abilities ?? [],
|
|
2047
|
+
callbackUrl: webhook.callbackUrl,
|
|
2048
|
+
secret: webhook.secret
|
|
2049
|
+
})
|
|
2050
|
+
});
|
|
2051
|
+
if (!response.ok) {
|
|
2052
|
+
return err(
|
|
2053
|
+
await responseError("hooks", "failed to register webhook", response)
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
const data = normalizeWebhookRecord(await response.json());
|
|
2057
|
+
if (!data) {
|
|
2058
|
+
return err(
|
|
2059
|
+
wrapError(
|
|
2060
|
+
"hooks",
|
|
2061
|
+
new Error("Webhook registration response did not include a record")
|
|
2062
|
+
)
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
return ok(data);
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
return err(wrapError("hooks", error));
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async list(options = {}) {
|
|
2071
|
+
if (!this.requireAuth()) {
|
|
2072
|
+
return err(authRequiredError("hooks"));
|
|
2073
|
+
}
|
|
2074
|
+
try {
|
|
2075
|
+
const query = new URLSearchParams();
|
|
2076
|
+
if (options.space) {
|
|
2077
|
+
query.set("space", options.space);
|
|
2078
|
+
}
|
|
2079
|
+
if (options.service) {
|
|
2080
|
+
query.set("service", options.service);
|
|
2081
|
+
}
|
|
2082
|
+
if (options.pathPrefix) {
|
|
2083
|
+
const normalizedPrefix = normalizePathPrefix(options.pathPrefix);
|
|
2084
|
+
if (normalizedPrefix) {
|
|
2085
|
+
query.set("prefix", normalizedPrefix);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
const response = await this.context.fetch(
|
|
2089
|
+
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
2090
|
+
{
|
|
2091
|
+
method: "GET",
|
|
2092
|
+
headers: serviceHeadersToRecord(
|
|
2093
|
+
this.createHookHeaders(
|
|
2094
|
+
"tinycloud.hooks/list",
|
|
2095
|
+
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
2096
|
+
)
|
|
2097
|
+
)
|
|
2098
|
+
}
|
|
2099
|
+
);
|
|
2100
|
+
if (!response.ok) {
|
|
2101
|
+
return err(
|
|
2102
|
+
await responseError("hooks", "failed to list webhooks", response)
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
const payload = await response.json();
|
|
2106
|
+
const records = normalizeWebhookRecordList(payload);
|
|
2107
|
+
if (!records) {
|
|
2108
|
+
return err(
|
|
2109
|
+
wrapError(
|
|
2110
|
+
"hooks",
|
|
2111
|
+
new Error("Webhook list response did not include records")
|
|
2112
|
+
)
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
return ok(records);
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
return err(wrapError("hooks", error));
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
async unregister(id, options = {}) {
|
|
2121
|
+
if (!this.requireAuth()) {
|
|
2122
|
+
return err(authRequiredError("hooks"));
|
|
2123
|
+
}
|
|
2124
|
+
try {
|
|
2125
|
+
const response = await this.context.fetch(
|
|
2126
|
+
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
2127
|
+
{
|
|
2128
|
+
method: "DELETE",
|
|
2129
|
+
headers: serviceHeadersToRecord(
|
|
2130
|
+
this.createHookHeaders(
|
|
2131
|
+
"tinycloud.hooks/unregister",
|
|
2132
|
+
options.target ? buildScopePath(
|
|
2133
|
+
options.target.service,
|
|
2134
|
+
options.target.pathPrefix
|
|
2135
|
+
) : `webhooks/${id}`
|
|
2136
|
+
)
|
|
2137
|
+
)
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
if (!response.ok) {
|
|
2141
|
+
return err(
|
|
2142
|
+
await responseError(
|
|
2143
|
+
"hooks",
|
|
2144
|
+
"failed to unregister webhook",
|
|
2145
|
+
response
|
|
2146
|
+
)
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
return ok(void 0);
|
|
2150
|
+
} catch (error) {
|
|
2151
|
+
return err(wrapError("hooks", error));
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
async scheduleSharedStreamRefresh() {
|
|
2155
|
+
this._refreshChain = this._refreshChain.then(() => this.refreshSharedStream()).catch(() => void 0);
|
|
2156
|
+
await this._refreshChain;
|
|
2157
|
+
}
|
|
2158
|
+
async refreshSharedStream() {
|
|
2159
|
+
if (!this.requireAuth() || this._subscribers.size === 0) {
|
|
2160
|
+
this.abortSharedStream();
|
|
2161
|
+
this._activeSignature = "";
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const state = this.collectSharedStreamState();
|
|
2165
|
+
if (state.signature !== this._activeSignature) {
|
|
2166
|
+
this._activeSignature = state.signature;
|
|
2167
|
+
this.abortSharedStream();
|
|
2168
|
+
}
|
|
2169
|
+
if (!this._sharedStreamTask) {
|
|
2170
|
+
this._sharedStreamTask = this.runSharedStream(state).catch((error) => {
|
|
2171
|
+
if (!isAbortError(error)) {
|
|
2172
|
+
throw error;
|
|
2173
|
+
}
|
|
2174
|
+
}).finally(() => {
|
|
2175
|
+
this._sharedStreamTask = void 0;
|
|
2176
|
+
this._sharedStreamAbort = void 0;
|
|
2177
|
+
if (this._subscribers.size > 0) {
|
|
2178
|
+
void this.scheduleSharedStreamRefresh();
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
collectSharedStreamState() {
|
|
2184
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2185
|
+
const ttlCandidates = [];
|
|
2186
|
+
for (const subscriber of this._subscribers) {
|
|
2187
|
+
if (typeof subscriber.ttlSeconds === "number") {
|
|
2188
|
+
ttlCandidates.push(subscriber.ttlSeconds);
|
|
2189
|
+
}
|
|
2190
|
+
for (const subscription of subscriber.requested) {
|
|
2191
|
+
merged.set(subscriptionSignature(subscription), subscription);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
const subscriptions = [...merged.values()].sort(
|
|
2195
|
+
(left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
|
|
2196
|
+
);
|
|
2197
|
+
const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
|
|
2198
|
+
const signature = JSON.stringify({
|
|
2199
|
+
subscriptions: subscriptions.map(subscriptionSignature),
|
|
2200
|
+
ttlSeconds
|
|
2201
|
+
});
|
|
2202
|
+
return {
|
|
2203
|
+
subscriptions,
|
|
2204
|
+
ttlSeconds,
|
|
2205
|
+
signature
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
async runSharedStream(state) {
|
|
2209
|
+
const abortController = new AbortController();
|
|
2210
|
+
this._sharedStreamAbort = abortController;
|
|
2211
|
+
try {
|
|
2212
|
+
const host = this._config.host ?? this.context.hosts[0];
|
|
2213
|
+
const ticketResponse = await this.mintHookTicket(
|
|
2214
|
+
state.subscriptions,
|
|
2215
|
+
state.ttlSeconds,
|
|
2216
|
+
abortController.signal
|
|
2217
|
+
);
|
|
2218
|
+
const streamResponse = await this.openHookStream(
|
|
2219
|
+
host,
|
|
2220
|
+
ticketResponse.ticket,
|
|
2221
|
+
abortController.signal
|
|
2222
|
+
);
|
|
2223
|
+
for await (const message of parseSseStream(
|
|
2224
|
+
streamResponse.body,
|
|
2225
|
+
abortController.signal
|
|
2226
|
+
)) {
|
|
2227
|
+
if (!message.data) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
const event = parseHookEvent(message);
|
|
2231
|
+
for (const subscriber of this._subscribers) {
|
|
2232
|
+
if (matchesAnySubscription(event, subscriber.requested)) {
|
|
2233
|
+
subscriber.queue.push(event);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
} finally {
|
|
2238
|
+
if (this._sharedStreamAbort === abortController) {
|
|
2239
|
+
this._sharedStreamAbort = void 0;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
abortSharedStream() {
|
|
2244
|
+
this._sharedStreamAbort?.abort();
|
|
2245
|
+
}
|
|
2246
|
+
createHookHeaders(action, path) {
|
|
2247
|
+
return this.context.invoke(this.session, "hooks", path, action);
|
|
2248
|
+
}
|
|
2249
|
+
async mintHookTicket(subscriptions, ttlSeconds, signal) {
|
|
2250
|
+
const host = this._config.host ?? this.context.hosts[0];
|
|
2251
|
+
const headers = this.createInvokeHeaders(subscriptions);
|
|
2252
|
+
const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
|
|
2253
|
+
method: "POST",
|
|
2254
|
+
headers: {
|
|
2255
|
+
...serviceHeadersToRecord(headers),
|
|
2256
|
+
"content-type": "application/json"
|
|
2257
|
+
},
|
|
2258
|
+
body: JSON.stringify({
|
|
2259
|
+
subscriptions,
|
|
2260
|
+
ttlSeconds
|
|
2261
|
+
}),
|
|
2262
|
+
signal
|
|
2263
|
+
});
|
|
2264
|
+
if (!ticketResponse.ok) {
|
|
2265
|
+
throw await responseError(
|
|
2266
|
+
"hooks",
|
|
2267
|
+
"failed to mint hook ticket",
|
|
2268
|
+
ticketResponse
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
const ticketJson = await ticketResponse.json();
|
|
2272
|
+
if (!ticketJson?.ticket) {
|
|
2273
|
+
throw new Error("Hook ticket response did not include a ticket");
|
|
2274
|
+
}
|
|
2275
|
+
return ticketJson;
|
|
2276
|
+
}
|
|
2277
|
+
async openHookStream(host, ticket, signal) {
|
|
2278
|
+
const streamResponse = await this.context.fetch(
|
|
2279
|
+
`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`,
|
|
2280
|
+
{
|
|
2281
|
+
method: "GET",
|
|
2282
|
+
headers: { accept: "text/event-stream" },
|
|
2283
|
+
signal
|
|
2284
|
+
}
|
|
2285
|
+
);
|
|
2286
|
+
if (!streamResponse.ok) {
|
|
2287
|
+
throw await responseError(
|
|
2288
|
+
"hooks",
|
|
2289
|
+
"failed to open hook stream",
|
|
2290
|
+
streamResponse
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
return streamResponse;
|
|
2294
|
+
}
|
|
2295
|
+
createInvokeHeaders(subscriptions) {
|
|
2296
|
+
const entries = subscriptions.map((subscription) => ({
|
|
2297
|
+
spaceId: subscription.space,
|
|
2298
|
+
service: "hooks",
|
|
2299
|
+
path: subscription.pathPrefix ? `${subscription.service}/${subscription.pathPrefix}` : subscription.service,
|
|
2300
|
+
action: "tinycloud.hooks/subscribe"
|
|
2301
|
+
}));
|
|
2302
|
+
if (this.context.invokeAny) {
|
|
2303
|
+
return this.context.invokeAny(this.session, entries);
|
|
2304
|
+
}
|
|
2305
|
+
if (entries.length === 1) {
|
|
2306
|
+
const entry = entries[0];
|
|
2307
|
+
return this.context.invoke(
|
|
2308
|
+
this.session,
|
|
2309
|
+
entry.service,
|
|
2310
|
+
entry.path,
|
|
2311
|
+
entry.action
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
throw new Error(
|
|
2315
|
+
"This SDK runtime does not support multi-scope hook invocations"
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
HooksService.serviceName = "hooks";
|
|
2320
|
+
function buildScopePath(service, pathPrefix) {
|
|
2321
|
+
const normalized = normalizePathPrefix(pathPrefix);
|
|
2322
|
+
return normalized ? `${service}/${normalized}` : service;
|
|
2323
|
+
}
|
|
2324
|
+
function normalizeSubscription(subscription) {
|
|
2325
|
+
return {
|
|
2326
|
+
...subscription,
|
|
2327
|
+
pathPrefix: normalizePathPrefix(subscription.pathPrefix),
|
|
2328
|
+
abilities: subscription.abilities ?? []
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
function subscriptionSignature(subscription) {
|
|
2332
|
+
return JSON.stringify({
|
|
2333
|
+
space: subscription.space,
|
|
2334
|
+
service: subscription.service,
|
|
2335
|
+
pathPrefix: subscription.pathPrefix ?? "",
|
|
2336
|
+
abilities: [...subscription.abilities ?? []].sort()
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
function matchesAnySubscription(event, subscriptions) {
|
|
2340
|
+
return subscriptions.some(
|
|
2341
|
+
(subscription) => matchesSubscription(event, subscription)
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
function matchesSubscription(event, subscription) {
|
|
2345
|
+
if (event.space !== subscription.space) {
|
|
2346
|
+
return false;
|
|
2347
|
+
}
|
|
2348
|
+
if (event.service !== subscription.service) {
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
if (subscription.pathPrefix) {
|
|
2352
|
+
const prefix = subscription.pathPrefix.endsWith("/") ? subscription.pathPrefix : `${subscription.pathPrefix}/`;
|
|
2353
|
+
if (event.path && event.path !== subscription.pathPrefix && !event.path.startsWith(prefix)) {
|
|
2354
|
+
return false;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const abilities = subscription.abilities ?? [];
|
|
2358
|
+
if (abilities.length > 0 && !abilities.includes(event.ability)) {
|
|
2359
|
+
return false;
|
|
2360
|
+
}
|
|
2361
|
+
return true;
|
|
2362
|
+
}
|
|
2363
|
+
function normalizePathPrefix(pathPrefix) {
|
|
2364
|
+
if (!pathPrefix) {
|
|
2365
|
+
return void 0;
|
|
2366
|
+
}
|
|
2367
|
+
const trimmed = pathPrefix.replace(/^\/+|\/+$/g, "");
|
|
2368
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2369
|
+
}
|
|
2370
|
+
function serviceHeadersToRecord(headers) {
|
|
2371
|
+
if (Array.isArray(headers)) {
|
|
2372
|
+
return Object.fromEntries(headers);
|
|
2373
|
+
}
|
|
2374
|
+
return { ...headers };
|
|
2375
|
+
}
|
|
2376
|
+
async function responseError(service, message, response) {
|
|
2377
|
+
let detail = response.statusText;
|
|
2378
|
+
try {
|
|
2379
|
+
const text = await response.text();
|
|
2380
|
+
if (text) {
|
|
2381
|
+
detail = text;
|
|
2382
|
+
}
|
|
2383
|
+
} catch {
|
|
2384
|
+
}
|
|
2385
|
+
return wrapError(
|
|
2386
|
+
service,
|
|
2387
|
+
new Error(`${message}: ${response.status} ${detail}`)
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
function isAbortError(error) {
|
|
2391
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
2392
|
+
}
|
|
2393
|
+
async function* parseSseStream(body, signal) {
|
|
2394
|
+
if (!body) {
|
|
2395
|
+
throw new Error("Hook stream response does not expose a readable body");
|
|
2396
|
+
}
|
|
2397
|
+
const decoder = new TextDecoder();
|
|
2398
|
+
let buffer = "";
|
|
2399
|
+
for await (const chunk of readBodyChunks(body, signal)) {
|
|
2400
|
+
buffer += decoder.decode(chunk, { stream: true }).replace(/\r\n/g, "\n");
|
|
2401
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
2402
|
+
while (separatorIndex >= 0) {
|
|
2403
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
2404
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
2405
|
+
const parsed = parseSseEvent(rawEvent);
|
|
2406
|
+
if (parsed) {
|
|
2407
|
+
yield parsed;
|
|
2408
|
+
}
|
|
2409
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
buffer += decoder.decode();
|
|
2413
|
+
const trailing = parseSseEvent(buffer.trim());
|
|
2414
|
+
if (trailing) {
|
|
2415
|
+
yield trailing;
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
async function* readBodyChunks(body, signal) {
|
|
2419
|
+
const asyncIterable = body;
|
|
2420
|
+
if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
|
|
2421
|
+
for await (const chunk of asyncIterable) {
|
|
2422
|
+
if (signal?.aborted) {
|
|
2423
|
+
break;
|
|
2424
|
+
}
|
|
2425
|
+
yield chunk;
|
|
2426
|
+
}
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
const stream = body;
|
|
2430
|
+
if (typeof stream.getReader !== "function") {
|
|
2431
|
+
throw new Error("Unsupported hook stream body type");
|
|
2432
|
+
}
|
|
2433
|
+
const reader = stream.getReader();
|
|
2434
|
+
try {
|
|
2435
|
+
while (!signal?.aborted) {
|
|
2436
|
+
const { done, value } = await reader.read();
|
|
2437
|
+
if (done) {
|
|
2438
|
+
break;
|
|
2439
|
+
}
|
|
2440
|
+
if (value) {
|
|
2441
|
+
yield value;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
} finally {
|
|
2445
|
+
try {
|
|
2446
|
+
await reader.cancel?.();
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
reader.releaseLock?.();
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function parseSseEvent(rawEvent) {
|
|
2453
|
+
if (!rawEvent) {
|
|
2454
|
+
return null;
|
|
2455
|
+
}
|
|
2456
|
+
let event = "message";
|
|
2457
|
+
let id;
|
|
2458
|
+
const dataLines = [];
|
|
2459
|
+
for (const line of rawEvent.split("\n")) {
|
|
2460
|
+
if (!line || line.startsWith(":")) {
|
|
2461
|
+
continue;
|
|
2462
|
+
}
|
|
2463
|
+
const [field, ...rest] = line.split(":");
|
|
2464
|
+
const value = rest.join(":").replace(/^ /, "");
|
|
2465
|
+
switch (field) {
|
|
2466
|
+
case "event":
|
|
2467
|
+
event = value;
|
|
2468
|
+
break;
|
|
2469
|
+
case "id":
|
|
2470
|
+
id = value;
|
|
2471
|
+
break;
|
|
2472
|
+
case "data":
|
|
2473
|
+
dataLines.push(value);
|
|
2474
|
+
break;
|
|
2475
|
+
default:
|
|
2476
|
+
break;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (dataLines.length === 0) {
|
|
2480
|
+
return null;
|
|
2481
|
+
}
|
|
2482
|
+
return {
|
|
2483
|
+
event,
|
|
2484
|
+
id,
|
|
2485
|
+
data: dataLines.join("\n")
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function parseHookEvent(message) {
|
|
2489
|
+
const parsed = JSON.parse(message.data);
|
|
2490
|
+
return {
|
|
2491
|
+
type: "write",
|
|
2492
|
+
id: parsed.id ?? message.id ?? "",
|
|
2493
|
+
space: parsed.space ?? "",
|
|
2494
|
+
service: parsed.service ?? "",
|
|
2495
|
+
ability: parsed.ability ?? "",
|
|
2496
|
+
path: parsed.path,
|
|
2497
|
+
actor: parsed.actor ?? "",
|
|
2498
|
+
epoch: parsed.epoch ?? "",
|
|
2499
|
+
eventIndex: parsed.eventIndex ?? 0,
|
|
2500
|
+
timestamp: parsed.timestamp ?? ""
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
function normalizeWebhookRecord(data) {
|
|
2504
|
+
if (!data || typeof data !== "object") {
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
const record = isRecordContainer(data);
|
|
2508
|
+
const candidate = pickWebhookRecord(record) ?? normalizeWebhookRecord(record.webhook) ?? normalizeWebhookRecord(record.hook) ?? normalizeWebhookRecord(record.subscription) ?? normalizeWebhookRecord(record.data);
|
|
2509
|
+
return candidate ?? null;
|
|
2510
|
+
}
|
|
2511
|
+
function normalizeWebhookRecordList(data) {
|
|
2512
|
+
if (Array.isArray(data)) {
|
|
2513
|
+
const records = data.map((entry) => normalizeWebhookRecord(entry)).filter((entry) => entry !== null);
|
|
2514
|
+
return records;
|
|
2515
|
+
}
|
|
2516
|
+
if (!data || typeof data !== "object") {
|
|
2517
|
+
return null;
|
|
2518
|
+
}
|
|
2519
|
+
const record = isRecordContainer(data);
|
|
2520
|
+
const nested = maybeRecordArray(record.webhooks) ?? maybeRecordArray(record.subscriptions) ?? maybeRecordArray(record.hooks) ?? maybeRecordArray(record.data);
|
|
2521
|
+
if (nested) {
|
|
2522
|
+
return nested;
|
|
2523
|
+
}
|
|
2524
|
+
const single = pickWebhookRecord(record);
|
|
2525
|
+
return single ? [single] : null;
|
|
2526
|
+
}
|
|
2527
|
+
function maybeRecordArray(value) {
|
|
2528
|
+
if (!Array.isArray(value)) {
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
const records = value.map((entry) => normalizeWebhookRecord(entry)).filter((entry) => entry !== null);
|
|
2532
|
+
return records;
|
|
2533
|
+
}
|
|
2534
|
+
function pickWebhookRecord(value) {
|
|
2535
|
+
const id = stringField(value, "id");
|
|
2536
|
+
const space = stringField(value, "space") ?? stringField(value, "spaceId");
|
|
2537
|
+
const service = stringField(value, "service");
|
|
2538
|
+
const callbackUrl = stringField(value, "callbackUrl") ?? stringField(value, "callback_url");
|
|
2539
|
+
if (!id || !space || !service || !callbackUrl) {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
id,
|
|
2544
|
+
space,
|
|
2545
|
+
service,
|
|
2546
|
+
pathPrefix: optionalStringField(value, "pathPrefix") ?? optionalStringField(value, "path_prefix"),
|
|
2547
|
+
abilities: stringArrayField(value, "abilities") ?? parsedStringArrayField(value, "abilitiesJson") ?? parsedStringArrayField(value, "abilities_json"),
|
|
2548
|
+
callbackUrl,
|
|
2549
|
+
active: booleanField(value, "active") ?? true,
|
|
2550
|
+
createdAt: stringField(value, "createdAt") ?? stringField(value, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2551
|
+
subscriberDid: optionalStringField(value, "subscriberDid") ?? optionalStringField(value, "subscriber_did")
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
function isRecordContainer(value) {
|
|
2555
|
+
return value;
|
|
2556
|
+
}
|
|
2557
|
+
function stringField(value, key) {
|
|
2558
|
+
const field = value[key];
|
|
2559
|
+
return typeof field === "string" ? field : void 0;
|
|
2560
|
+
}
|
|
2561
|
+
function optionalStringField(value, key) {
|
|
2562
|
+
return stringField(value, key);
|
|
2563
|
+
}
|
|
2564
|
+
function booleanField(value, key) {
|
|
2565
|
+
const field = value[key];
|
|
2566
|
+
return typeof field === "boolean" ? field : void 0;
|
|
2567
|
+
}
|
|
2568
|
+
function stringArrayField(value, key) {
|
|
2569
|
+
const field = value[key];
|
|
2570
|
+
if (!Array.isArray(field)) {
|
|
2571
|
+
return void 0;
|
|
2572
|
+
}
|
|
2573
|
+
const strings = field.filter(
|
|
2574
|
+
(item) => typeof item === "string"
|
|
2575
|
+
);
|
|
2576
|
+
return strings.length === field.length ? strings : void 0;
|
|
2577
|
+
}
|
|
2578
|
+
function parsedStringArrayField(value, key) {
|
|
2579
|
+
const field = value[key];
|
|
2580
|
+
if (typeof field !== "string") {
|
|
2581
|
+
return void 0;
|
|
2582
|
+
}
|
|
2583
|
+
try {
|
|
2584
|
+
const parsed = JSON.parse(field);
|
|
2585
|
+
if (!Array.isArray(parsed)) {
|
|
2586
|
+
return void 0;
|
|
2587
|
+
}
|
|
2588
|
+
const strings = parsed.filter(
|
|
2589
|
+
(item) => typeof item === "string"
|
|
2590
|
+
);
|
|
2591
|
+
return strings.length === parsed.length ? strings : void 0;
|
|
2592
|
+
} catch {
|
|
2593
|
+
return void 0;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
1913
2597
|
// src/quota/TinyCloudQuota.ts
|
|
1914
2598
|
var TinyCloudQuota = class {
|
|
1915
2599
|
constructor(config = {}) {
|
|
@@ -3088,6 +3772,7 @@ export {
|
|
|
3088
3772
|
ErrorCodes,
|
|
3089
3773
|
GenericKVResponseSchema,
|
|
3090
3774
|
GenericResultSchema,
|
|
3775
|
+
HooksService,
|
|
3091
3776
|
KVAction,
|
|
3092
3777
|
KVListResponseSchema,
|
|
3093
3778
|
KVListResultSchema,
|