@tinycloud/sdk-services 2.0.4 → 2.1.0-beta.0

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.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,