@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/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,