@oatscenter/trellis 0.100.0-rc.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.
Files changed (264) hide show
  1. package/README.md +59 -0
  2. package/auth/authorization/client_context.d.ts +61 -0
  3. package/auth/authorization/client_context.js +266 -0
  4. package/auth/authorization/nats_registry.d.ts +41 -0
  5. package/auth/authorization/nats_registry.js +283 -0
  6. package/auth/authorization/provider_cache.d.ts +90 -0
  7. package/auth/authorization/provider_cache.js +783 -0
  8. package/auth/authorization/refresh.d.ts +46 -0
  9. package/auth/authorization/refresh.js +246 -0
  10. package/auth/authorization/types.d.ts +207 -0
  11. package/auth/authorization/types.js +60 -0
  12. package/auth/authorization_context.d.ts +11 -0
  13. package/auth/authorization_context.js +10 -0
  14. package/auth/browser/flow_types.d.ts +84 -0
  15. package/auth/browser/flow_types.js +70 -0
  16. package/auth/browser/portal.d.ts +21 -0
  17. package/auth/browser/portal.js +307 -0
  18. package/auth/browser/storage.d.ts +26 -0
  19. package/auth/browser/storage.js +224 -0
  20. package/auth/browser.d.ts +11 -0
  21. package/auth/browser.js +10 -0
  22. package/auth/browser_recovery.d.ts +13 -0
  23. package/auth/browser_recovery.js +38 -0
  24. package/auth/device_activation.d.ts +134 -0
  25. package/auth/device_activation.js +376 -0
  26. package/auth/http_error.d.ts +12 -0
  27. package/auth/http_error.js +32 -0
  28. package/auth/keys.d.ts +5 -0
  29. package/auth/keys.js +63 -0
  30. package/auth/mod.d.ts +16 -0
  31. package/auth/mod.js +16 -0
  32. package/auth/proof.d.ts +31 -0
  33. package/auth/proof.js +95 -0
  34. package/auth/protocol_wasm/trellis_protocol_wasm.d.ts +138 -0
  35. package/auth/protocol_wasm/trellis_protocol_wasm.js +605 -0
  36. package/auth/protocol_wasm/trellis_protocol_wasm_bg.wasm +0 -0
  37. package/auth/protocol_wasm/trellis_protocol_wasm_bytes.d.ts +1 -0
  38. package/auth/protocol_wasm/trellis_protocol_wasm_bytes.js +2 -0
  39. package/auth/protocol_wasm.d.ts +179 -0
  40. package/auth/protocol_wasm.js +133 -0
  41. package/auth/session_auth.d.ts +30 -0
  42. package/auth/session_auth.js +77 -0
  43. package/auth/session_proof.d.ts +53 -0
  44. package/auth/session_proof.js +43 -0
  45. package/auth/time.d.ts +6 -0
  46. package/auth/time.js +7 -0
  47. package/auth/trellis_id.d.ts +1 -0
  48. package/auth/trellis_id.js +5 -0
  49. package/auth/utils.d.ts +10 -0
  50. package/auth/utils.js +69 -0
  51. package/auth.d.ts +1 -0
  52. package/auth.js +1 -0
  53. package/caller.d.ts +111 -0
  54. package/caller.js +85 -0
  55. package/client.d.ts +14 -0
  56. package/client.js +1 -0
  57. package/client_connect.d.ts +143 -0
  58. package/client_connect.js +1097 -0
  59. package/codec.d.ts +13 -0
  60. package/codec.js +192 -0
  61. package/connection.d.ts +107 -0
  62. package/connection.js +396 -0
  63. package/device/activation.d.ts +42 -0
  64. package/device/activation.js +319 -0
  65. package/device.d.ts +80 -0
  66. package/device.js +687 -0
  67. package/env.d.ts +2 -0
  68. package/env.js +10 -0
  69. package/errors/AuthError.d.ts +30 -0
  70. package/errors/AuthError.js +34 -0
  71. package/errors/KVError.d.ts +30 -0
  72. package/errors/KVError.js +36 -0
  73. package/errors/OperationLifecycleError.d.ts +110 -0
  74. package/errors/OperationLifecycleError.js +142 -0
  75. package/errors/RemoteError.d.ts +61 -0
  76. package/errors/RemoteError.js +72 -0
  77. package/errors/SchemaValidationError.d.ts +48 -0
  78. package/errors/SchemaValidationError.js +49 -0
  79. package/errors/StoreError.d.ts +21 -0
  80. package/errors/StoreError.js +31 -0
  81. package/errors/TransferError.d.ts +21 -0
  82. package/errors/TransferError.js +31 -0
  83. package/errors/TransportError.d.ts +29 -0
  84. package/errors/TransportError.js +33 -0
  85. package/errors/TrellisError.d.ts +15 -0
  86. package/errors/TrellisError.js +15 -0
  87. package/errors/ValidationError.d.ts +50 -0
  88. package/errors/ValidationError.js +59 -0
  89. package/errors/index.d.ts +254 -0
  90. package/errors/index.js +238 -0
  91. package/generated.d.ts +4 -0
  92. package/generated.js +3 -0
  93. package/generated_support.d.ts +120 -0
  94. package/generated_support.js +359 -0
  95. package/globals.d.ts +9 -0
  96. package/globals.js +16 -0
  97. package/health_transport.d.ts +18 -0
  98. package/health_transport.js +32 -0
  99. package/helpers.d.ts +11 -0
  100. package/helpers.js +53 -0
  101. package/index.d.ts +2 -0
  102. package/index.js +2 -0
  103. package/internal_sdk/generated/apis/auth/mod.d.ts +805 -0
  104. package/internal_sdk/generated/apis/auth/mod.js +958 -0
  105. package/internal_sdk/generated/apis/core/mod.d.ts +79 -0
  106. package/internal_sdk/generated/apis/core/mod.js +93 -0
  107. package/internal_sdk/generated/apis/events/mod.d.ts +224 -0
  108. package/internal_sdk/generated/apis/events/mod.js +313 -0
  109. package/internal_sdk/generated/apis/health/mod.d.ts +106 -0
  110. package/internal_sdk/generated/apis/health/mod.js +132 -0
  111. package/internal_sdk/generated/apis/index.d.ts +7 -0
  112. package/internal_sdk/generated/apis/index.js +8 -0
  113. package/internal_sdk/generated/apis/jobs/mod.d.ts +176 -0
  114. package/internal_sdk/generated/apis/jobs/mod.js +209 -0
  115. package/internal_sdk/generated/apis/state/mod.d.ts +104 -0
  116. package/internal_sdk/generated/apis/state/mod.js +139 -0
  117. package/internal_sdk/generated/index.d.ts +4 -0
  118. package/internal_sdk/generated/index.js +5 -0
  119. package/internal_sdk/generated/package.json +18 -0
  120. package/internal_sdk/generated/participants/cli/mod.d.ts +402 -0
  121. package/internal_sdk/generated/participants/cli/mod.js +460 -0
  122. package/internal_sdk/generated/participants/cli/types.d.ts +2 -0
  123. package/internal_sdk/generated/participants/cli/types.js +3 -0
  124. package/internal_sdk/generated/participants/console/mod.d.ts +489 -0
  125. package/internal_sdk/generated/participants/console/mod.js +579 -0
  126. package/internal_sdk/generated/participants/console/types.d.ts +2 -0
  127. package/internal_sdk/generated/participants/console/types.js +3 -0
  128. package/internal_sdk/generated/participants/eventsRuntime/mod.d.ts +64 -0
  129. package/internal_sdk/generated/participants/eventsRuntime/mod.js +42 -0
  130. package/internal_sdk/generated/participants/eventsRuntime/types.d.ts +2 -0
  131. package/internal_sdk/generated/participants/eventsRuntime/types.js +3 -0
  132. package/internal_sdk/generated/participants/healthRuntime/mod.d.ts +58 -0
  133. package/internal_sdk/generated/participants/healthRuntime/mod.js +36 -0
  134. package/internal_sdk/generated/participants/healthRuntime/types.d.ts +2 -0
  135. package/internal_sdk/generated/participants/healthRuntime/types.js +3 -0
  136. package/internal_sdk/generated/participants/index.d.ts +8 -0
  137. package/internal_sdk/generated/participants/index.js +9 -0
  138. package/internal_sdk/generated/participants/jobsRuntime/mod.d.ts +64 -0
  139. package/internal_sdk/generated/participants/jobsRuntime/mod.js +42 -0
  140. package/internal_sdk/generated/participants/jobsRuntime/types.d.ts +2 -0
  141. package/internal_sdk/generated/participants/jobsRuntime/types.js +3 -0
  142. package/internal_sdk/generated/participants/platform/mod.d.ts +164 -0
  143. package/internal_sdk/generated/participants/platform/mod.js +149 -0
  144. package/internal_sdk/generated/participants/platform/types.d.ts +7 -0
  145. package/internal_sdk/generated/participants/platform/types.js +5 -0
  146. package/internal_sdk/generated/participants/portal/mod.d.ts +131 -0
  147. package/internal_sdk/generated/participants/portal/mod.js +109 -0
  148. package/internal_sdk/generated/participants/portal/types.d.ts +2 -0
  149. package/internal_sdk/generated/participants/portal/types.js +3 -0
  150. package/internal_sdk/generated/types/_internal/p0.d.ts +13511 -0
  151. package/internal_sdk/generated/types/_internal/p0.js +6105 -0
  152. package/internal_sdk/generated/types/index.d.ts +2951 -0
  153. package/internal_sdk/generated/types/index.js +1477 -0
  154. package/jobs.d.ts +309 -0
  155. package/jobs.js +389 -0
  156. package/kv.d.ts +85 -0
  157. package/kv.js +330 -0
  158. package/operations.d.ts +189 -0
  159. package/operations.js +847 -0
  160. package/package.json +73 -0
  161. package/pagination.d.ts +25 -0
  162. package/pagination.js +117 -0
  163. package/participant.d.ts +6 -0
  164. package/participant.js +6 -0
  165. package/participant_runtime/api.d.ts +131 -0
  166. package/participant_runtime/api.js +42 -0
  167. package/participant_runtime/json.d.ts +16 -0
  168. package/participant_runtime/json.js +193 -0
  169. package/participant_runtime/metadata.d.ts +34 -0
  170. package/participant_runtime/metadata.js +4 -0
  171. package/participant_runtime/participant.d.ts +200 -0
  172. package/participant_runtime/participant.js +330 -0
  173. package/participant_runtime/schemas.d.ts +347 -0
  174. package/participant_runtime/schemas.js +211 -0
  175. package/participant_runtime/surface_names.d.ts +16 -0
  176. package/participant_runtime/surface_names.js +34 -0
  177. package/portable.d.ts +30 -0
  178. package/portable.js +14 -0
  179. package/provider.d.ts +69 -0
  180. package/provider.js +68 -0
  181. package/runtime_transport.d.ts +25 -0
  182. package/runtime_transport.js +48 -0
  183. package/service/mod.d.ts +14 -0
  184. package/service/mod.js +13 -0
  185. package/service/outbox_inbox.d.ts +259 -0
  186. package/service/outbox_inbox.js +908 -0
  187. package/service/runtime/bootstrap.d.ts +56 -0
  188. package/service/runtime/bootstrap.js +226 -0
  189. package/service/runtime/core.d.ts +46 -0
  190. package/service/runtime/core.js +1747 -0
  191. package/service/runtime/health.d.ts +97 -0
  192. package/service/runtime/health.js +153 -0
  193. package/service/runtime/health_schemas.d.ts +23 -0
  194. package/service/runtime/health_schemas.js +20 -0
  195. package/service/runtime/internal_jobs/active-job.d.ts +40 -0
  196. package/service/runtime/internal_jobs/active-job.js +91 -0
  197. package/service/runtime/internal_jobs/bindings.d.ts +40 -0
  198. package/service/runtime/internal_jobs/bindings.js +1 -0
  199. package/service/runtime/internal_jobs/cancellation-registry.d.ts +13 -0
  200. package/service/runtime/internal_jobs/cancellation-registry.js +55 -0
  201. package/service/runtime/internal_jobs/heartbeat.d.ts +33 -0
  202. package/service/runtime/internal_jobs/heartbeat.js +65 -0
  203. package/service/runtime/internal_jobs/job-manager.d.ts +126 -0
  204. package/service/runtime/internal_jobs/job-manager.js +821 -0
  205. package/service/runtime/internal_jobs/key-coordinator.d.ts +280 -0
  206. package/service/runtime/internal_jobs/key-coordinator.js +747 -0
  207. package/service/runtime/internal_jobs/projection.d.ts +3 -0
  208. package/service/runtime/internal_jobs/projection.js +42 -0
  209. package/service/runtime/internal_jobs/runtime-worker.d.ts +126 -0
  210. package/service/runtime/internal_jobs/runtime-worker.js +454 -0
  211. package/service/runtime/internal_jobs/types.d.ts +246 -0
  212. package/service/runtime/internal_jobs/types.js +163 -0
  213. package/service/runtime/logger.d.ts +2 -0
  214. package/service/runtime/logger.js +7 -0
  215. package/service/runtime/runtime.d.ts +16 -0
  216. package/service/runtime/runtime.js +1 -0
  217. package/service/runtime/service.d.ts +372 -0
  218. package/service/runtime/service.js +1896 -0
  219. package/service/runtime/transfer/download.d.ts +27 -0
  220. package/service/runtime/transfer/download.js +1 -0
  221. package/service/runtime/transfer/protocol.d.ts +15 -0
  222. package/service/runtime/transfer/protocol.js +46 -0
  223. package/service/runtime/transfer/queue.d.ts +18 -0
  224. package/service/runtime/transfer/queue.js +136 -0
  225. package/service/runtime/transfer/upload.d.ts +40 -0
  226. package/service/runtime/transfer/upload.js +27 -0
  227. package/service/runtime/transfer.d.ts +62 -0
  228. package/service/runtime/transfer.js +763 -0
  229. package/session.d.ts +862 -0
  230. package/session.js +3407 -0
  231. package/store.d.ts +71 -0
  232. package/store.js +412 -0
  233. package/store_entry.d.ts +20 -0
  234. package/store_entry.js +75 -0
  235. package/tasks.d.ts +10 -0
  236. package/tasks.js +28 -0
  237. package/telemetry/carrier.d.ts +8 -0
  238. package/telemetry/carrier.js +20 -0
  239. package/telemetry/core.d.ts +7 -0
  240. package/telemetry/core.js +14 -0
  241. package/telemetry/env.d.ts +2 -0
  242. package/telemetry/env.js +10 -0
  243. package/telemetry/init.d.ts +2 -0
  244. package/telemetry/init.js +7 -0
  245. package/telemetry/metrics.d.ts +81 -0
  246. package/telemetry/metrics.js +267 -0
  247. package/telemetry/mod.d.ts +13 -0
  248. package/telemetry/mod.js +8 -0
  249. package/telemetry/nats.d.ts +6 -0
  250. package/telemetry/nats.js +6 -0
  251. package/telemetry/result.d.ts +1 -0
  252. package/telemetry/result.js +18 -0
  253. package/telemetry/runtime.d.ts +1 -0
  254. package/telemetry/runtime.js +141 -0
  255. package/telemetry/trace.d.ts +4 -0
  256. package/telemetry/trace.js +26 -0
  257. package/telemetry/trellis.d.ts +1 -0
  258. package/telemetry/trellis.js +1 -0
  259. package/telemetry.d.ts +1 -0
  260. package/telemetry.js +1 -0
  261. package/transfer.d.ts +112 -0
  262. package/transfer.js +525 -0
  263. package/transfer_protocol.d.ts +1 -0
  264. package/transfer_protocol.js +13 -0
package/store.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ import type { NatsConnection } from "@nats-io/nats-core/internal";
2
+ import { AsyncResult } from "@oatscenter/result";
3
+ import { StoreError } from "./errors/index.ts";
4
+ import { TypedStoreEntry } from "./store_entry.ts";
5
+ export { TypedStoreEntry } from "./store_entry.ts";
6
+ export type StoreBody = Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
7
+ export type StoreWaitOptions = {
8
+ timeoutMs?: number;
9
+ pollIntervalMs?: number;
10
+ signal?: AbortSignal;
11
+ };
12
+ export type StoreOpenOptions = {
13
+ ttlMs?: number;
14
+ maxObjectBytes?: number;
15
+ maxTotalBytes?: number;
16
+ bindOnly?: boolean;
17
+ isCurrent?: () => boolean;
18
+ };
19
+ export type StorePutOptions = {
20
+ contentType?: string;
21
+ metadata?: Record<string, string>;
22
+ };
23
+ /** Explicit bounded query for listing object metadata in a typed store. */
24
+ export type StoreListOptions = {
25
+ prefix?: string;
26
+ cursor?: string;
27
+ limit?: number;
28
+ };
29
+ /** One key-sorted page of object metadata and its opaque continuation. */
30
+ export type StoreListPage = {
31
+ entries: StoreInfo[];
32
+ nextCursor?: string;
33
+ };
34
+ export type StoreInfo = {
35
+ key: string;
36
+ size: number;
37
+ updatedAt: string;
38
+ digest?: string;
39
+ contentType?: string;
40
+ metadata: Record<string, string>;
41
+ };
42
+ export type StoreStatus = {
43
+ size: number;
44
+ sealed: boolean;
45
+ ttlMs: number;
46
+ maxObjectBytes?: number;
47
+ maxTotalBytes?: number;
48
+ };
49
+ type ExistingStoreStatus = {
50
+ ttl: number;
51
+ };
52
+ /** Verifies that an existing physical store matches its effective binding. */
53
+ export declare function ensureExistingStoreOptions(store: {
54
+ status(): Promise<ExistingStoreStatus>;
55
+ }, name: string, options: StoreOpenOptions): Promise<void>;
56
+ export declare class TypedStore {
57
+ #private;
58
+ private constructor();
59
+ static open(nats: NatsConnection, name: string, options?: StoreOpenOptions): AsyncResult<TypedStore, StoreError>;
60
+ create(key: string, body: StoreBody, options?: StorePutOptions): AsyncResult<void, StoreError>;
61
+ put(key: string, body: StoreBody, options?: StorePutOptions): AsyncResult<void, StoreError>;
62
+ get(key: string): AsyncResult<TypedStoreEntry, StoreError>;
63
+ /**
64
+ * Waits for an object key to appear in the store and returns the resulting entry.
65
+ */
66
+ waitFor(key: string, options?: StoreWaitOptions): AsyncResult<TypedStoreEntry, StoreError>;
67
+ delete(key: string): AsyncResult<void, StoreError>;
68
+ list(opts?: StoreListOptions): AsyncResult<StoreListPage, StoreError>;
69
+ status(): AsyncResult<StoreStatus, StoreError>;
70
+ }
71
+ export declare function bytesFromStoreStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array>;
package/store.js ADDED
@@ -0,0 +1,412 @@
1
+ import { Objm, } from "@nats-io/obj";
2
+ import { AsyncResult, Result, } from "@oatscenter/result";
3
+ import { StoreError } from "./errors/index.js";
4
+ import { decodePaginationCursor, encodePaginationCursor, paginationQueryDigest, } from "./auth/protocol_wasm.js";
5
+ import { TypedStoreEntry } from "./store_entry.js";
6
+ export { TypedStoreEntry } from "./store_entry.js";
7
+ const INTERNAL_CONTENT_TYPE_METADATA_KEY = "__trellis_content_type";
8
+ const DEFAULT_STORE_WAIT_POLL_INTERVAL_MS = 250;
9
+ const MAX_STORE_LIST_LIMIT = 500;
10
+ const DEFAULT_STORE_LIST_LIMIT = 100;
11
+ const STORE_LIST_CURSOR_ENDPOINT = "trellis.store.list";
12
+ function metadataWithContentType(options) {
13
+ if (!options?.metadata && !options?.contentType) {
14
+ return undefined;
15
+ }
16
+ return {
17
+ ...(options?.metadata ?? {}),
18
+ ...(options?.contentType
19
+ ? { [INTERNAL_CONTENT_TYPE_METADATA_KEY]: options.contentType }
20
+ : {}),
21
+ };
22
+ }
23
+ function storeInfoFromObjectInfo(info) {
24
+ const { [INTERNAL_CONTENT_TYPE_METADATA_KEY]: contentType, ...metadata } = info.metadata ?? {};
25
+ return {
26
+ key: info.name,
27
+ size: info.size,
28
+ updatedAt: info.mtime,
29
+ ...(info.digest ? { digest: info.digest } : {}),
30
+ ...(contentType ? { contentType } : {}),
31
+ metadata,
32
+ };
33
+ }
34
+ function compareStoreKeys(left, right) {
35
+ const leftBytes = new TextEncoder().encode(left);
36
+ const rightBytes = new TextEncoder().encode(right);
37
+ for (let index = 0; index < Math.min(leftBytes.length, rightBytes.length); index++) {
38
+ if (leftBytes[index] !== rightBytes[index]) {
39
+ return leftBytes[index] - rightBytes[index];
40
+ }
41
+ }
42
+ return leftBytes.length - rightBytes.length;
43
+ }
44
+ function streamFromAsyncIterable(iterable) {
45
+ const iterator = iterable[Symbol.asyncIterator]();
46
+ return new ReadableStream({
47
+ async pull(controller) {
48
+ const next = await iterator.next();
49
+ if (next.done) {
50
+ controller.close();
51
+ return;
52
+ }
53
+ controller.enqueue(next.value);
54
+ },
55
+ async cancel(reason) {
56
+ await iterator.return?.(reason);
57
+ },
58
+ });
59
+ }
60
+ function validateStoreListOptions(opts = {}) {
61
+ const limit = opts.limit ?? DEFAULT_STORE_LIST_LIMIT;
62
+ if (!Number.isInteger(limit) || limit <= 0) {
63
+ return Result.err(new StoreError({
64
+ operation: "list",
65
+ context: { reason: "invalid_limit", limit },
66
+ }));
67
+ }
68
+ if (limit > MAX_STORE_LIST_LIMIT) {
69
+ return Result.err(new StoreError({
70
+ operation: "list",
71
+ context: {
72
+ reason: "limit_exceeded",
73
+ limit,
74
+ maxLimit: MAX_STORE_LIST_LIMIT,
75
+ },
76
+ }));
77
+ }
78
+ const cursor = opts.cursor ?? "";
79
+ if (opts.cursor !== undefined && cursor.length === 0) {
80
+ return Result.err(new StoreError({
81
+ operation: "list",
82
+ context: { reason: "invalid_cursor" },
83
+ }));
84
+ }
85
+ return Result.ok({ prefix: opts.prefix ?? "", cursor, limit });
86
+ }
87
+ function enforceMaxObjectBytes(stream, maxObjectBytes) {
88
+ if (maxObjectBytes === undefined) {
89
+ return stream;
90
+ }
91
+ const reader = stream.getReader();
92
+ let totalBytes = 0;
93
+ return new ReadableStream({
94
+ async pull(controller) {
95
+ const next = await reader.read();
96
+ if (next.done) {
97
+ controller.close();
98
+ return;
99
+ }
100
+ totalBytes += next.value.length;
101
+ if (totalBytes > maxObjectBytes) {
102
+ controller.error(new StoreError({
103
+ operation: "put",
104
+ context: {
105
+ reason: "max_object_bytes_exceeded",
106
+ maxObjectBytes,
107
+ attemptedBytes: totalBytes,
108
+ },
109
+ }));
110
+ await reader.cancel();
111
+ return;
112
+ }
113
+ controller.enqueue(next.value);
114
+ },
115
+ async cancel(reason) {
116
+ await reader.cancel(reason);
117
+ },
118
+ });
119
+ }
120
+ async function bytesFromStream(stream) {
121
+ const reader = stream.getReader();
122
+ const chunks = [];
123
+ let totalLength = 0;
124
+ while (true) {
125
+ const next = await reader.read();
126
+ if (next.done) {
127
+ break;
128
+ }
129
+ chunks.push(next.value);
130
+ totalLength += next.value.length;
131
+ }
132
+ const merged = new Uint8Array(totalLength);
133
+ let offset = 0;
134
+ for (const chunk of chunks) {
135
+ merged.set(chunk, offset);
136
+ offset += chunk.length;
137
+ }
138
+ return merged;
139
+ }
140
+ function isNotFoundStoreError(error) {
141
+ return error.getContext().reason === "not_found";
142
+ }
143
+ function abortedStoreError(key, cause) {
144
+ return new StoreError({
145
+ operation: "waitFor",
146
+ cause,
147
+ context: { key, reason: "aborted" },
148
+ });
149
+ }
150
+ async function sleepWithSignal(ms, signal) {
151
+ if (signal?.aborted) {
152
+ throw signal.reason ??
153
+ new DOMException("The operation was aborted", "AbortError");
154
+ }
155
+ await new Promise((resolve, reject) => {
156
+ const timeoutId = setTimeout(() => {
157
+ cleanup();
158
+ resolve();
159
+ }, ms);
160
+ const onAbort = () => {
161
+ cleanup();
162
+ reject(signal?.reason ??
163
+ new DOMException("The operation was aborted", "AbortError"));
164
+ };
165
+ const cleanup = () => {
166
+ clearTimeout(timeoutId);
167
+ signal?.removeEventListener("abort", onAbort);
168
+ };
169
+ signal?.addEventListener("abort", onAbort, { once: true });
170
+ });
171
+ }
172
+ function streamFromBody(body) {
173
+ return body instanceof ReadableStream ? body : streamFromAsyncIterable(body);
174
+ }
175
+ async function unwrapObjectInfo(store, key) {
176
+ try {
177
+ const info = await store.info(key);
178
+ if (info === null || info.deleted) {
179
+ return Result.err(new StoreError({
180
+ operation: "get",
181
+ context: { key, reason: "not_found" },
182
+ }));
183
+ }
184
+ return Result.ok(info);
185
+ }
186
+ catch (cause) {
187
+ return Result.err(new StoreError({ operation: "get", cause, context: { key } }));
188
+ }
189
+ }
190
+ /** Verifies that an existing physical store matches its effective binding. */
191
+ export async function ensureExistingStoreOptions(store, name, options) {
192
+ const status = await store.status();
193
+ const actualTtlMs = status.ttl > 0 ? Math.floor(status.ttl / 1_000_000) : 0;
194
+ if (actualTtlMs !== (options.ttlMs ?? 0)) {
195
+ throw new Error(`Store '${name}' TTL does not match its binding`);
196
+ }
197
+ }
198
+ export class TypedStore {
199
+ #store;
200
+ #options;
201
+ constructor(store, options) {
202
+ this.#store = store;
203
+ this.#options = {
204
+ ttlMs: options.ttlMs ?? 0,
205
+ ...(options.maxObjectBytes !== undefined
206
+ ? { maxObjectBytes: options.maxObjectBytes }
207
+ : {}),
208
+ ...(options.maxTotalBytes !== undefined
209
+ ? { maxTotalBytes: options.maxTotalBytes }
210
+ : {}),
211
+ ...(options.bindOnly !== undefined ? { bindOnly: options.bindOnly } : {}),
212
+ };
213
+ }
214
+ static open(nats, name, options = {}) {
215
+ return AsyncResult.from((async () => {
216
+ try {
217
+ const objm = new Objm(nats);
218
+ const store = options.bindOnly
219
+ ? await objm.open(name)
220
+ : await objm.create(name, {
221
+ ...(options.ttlMs && options.ttlMs > 0
222
+ ? { ttl: options.ttlMs * 1_000_000 }
223
+ : {}),
224
+ ...(options.maxTotalBytes !== undefined
225
+ ? { max_bytes: options.maxTotalBytes }
226
+ : {}),
227
+ });
228
+ await ensureExistingStoreOptions(store, name, options);
229
+ return Result.ok(new TypedStore(store, options));
230
+ }
231
+ catch (cause) {
232
+ return Result.err(new StoreError({ operation: "open", cause, context: { name } }));
233
+ }
234
+ })());
235
+ }
236
+ create(key, body, options) {
237
+ return AsyncResult.from(this.#putInternal("create", key, body, options, 0));
238
+ }
239
+ put(key, body, options) {
240
+ return AsyncResult.from(this.#putInternal("put", key, body, options));
241
+ }
242
+ get(key) {
243
+ return AsyncResult.from((async () => {
244
+ if (!this.#isCurrent())
245
+ return this.#stale("get", key);
246
+ const info = await unwrapObjectInfo(this.#store, key);
247
+ return info.map((objectInfo) => new TypedStoreEntry(this.#store, storeInfoFromObjectInfo(objectInfo)));
248
+ })());
249
+ }
250
+ /**
251
+ * Waits for an object key to appear in the store and returns the resulting entry.
252
+ */
253
+ waitFor(key, options = {}) {
254
+ return AsyncResult.from((async () => {
255
+ const startedAt = Date.now();
256
+ const pollIntervalMs = options.pollIntervalMs ??
257
+ DEFAULT_STORE_WAIT_POLL_INTERVAL_MS;
258
+ while (true) {
259
+ if (options.signal?.aborted) {
260
+ return Result.err(abortedStoreError(key, options.signal.reason));
261
+ }
262
+ const entry = await this.get(key);
263
+ if (entry.isOk()) {
264
+ return entry;
265
+ }
266
+ if (!isNotFoundStoreError(entry.error)) {
267
+ return entry;
268
+ }
269
+ const remainingTimeoutMs = options.timeoutMs === undefined
270
+ ? undefined
271
+ : options.timeoutMs - (Date.now() - startedAt);
272
+ if (remainingTimeoutMs !== undefined && remainingTimeoutMs <= 0) {
273
+ return Result.err(new StoreError({
274
+ operation: "waitFor",
275
+ context: { key, reason: "timeout", timeoutMs: options.timeoutMs },
276
+ }));
277
+ }
278
+ try {
279
+ await sleepWithSignal(remainingTimeoutMs === undefined
280
+ ? pollIntervalMs
281
+ : Math.min(pollIntervalMs, remainingTimeoutMs), options.signal);
282
+ }
283
+ catch (cause) {
284
+ return Result.err(abortedStoreError(key, cause));
285
+ }
286
+ }
287
+ })());
288
+ }
289
+ delete(key) {
290
+ return AsyncResult.from((async () => {
291
+ if (!this.#isCurrent())
292
+ return this.#stale("delete", key);
293
+ try {
294
+ await this.#store.delete(key);
295
+ return Result.ok(undefined);
296
+ }
297
+ catch (cause) {
298
+ return Result.err(new StoreError({ operation: "delete", cause, context: { key } }));
299
+ }
300
+ })());
301
+ }
302
+ list(opts = {}) {
303
+ return AsyncResult.from((async () => {
304
+ if (!this.#isCurrent())
305
+ return this.#stale("list");
306
+ const query = validateStoreListOptions(opts);
307
+ if (query.isErr())
308
+ return Result.err(query.error);
309
+ const { prefix, cursor, limit } = query.unwrapOrElse(() => {
310
+ throw new Error("unreachable");
311
+ });
312
+ try {
313
+ const queryDigest = await paginationQueryDigest(STORE_LIST_CURSOR_ENDPOINT, { prefix });
314
+ let after = "";
315
+ if (cursor) {
316
+ const decoded = await decodePaginationCursor(cursor, queryDigest);
317
+ if (typeof decoded !== "string") {
318
+ throw new Error("invalid pagination cursor");
319
+ }
320
+ after = decoded;
321
+ }
322
+ const objects = await this.#store.list();
323
+ const filtered = objects
324
+ .filter((info) => !info.deleted && info.name.startsWith(prefix))
325
+ .map(storeInfoFromObjectInfo)
326
+ .sort((left, right) => compareStoreKeys(left.key, right.key))
327
+ .filter((info) => compareStoreKeys(info.key, after) > 0);
328
+ const entries = filtered.slice(0, limit);
329
+ return Result.ok({
330
+ entries,
331
+ ...(filtered.length > limit
332
+ ? {
333
+ nextCursor: await encodePaginationCursor(queryDigest, entries.at(-1)?.key),
334
+ }
335
+ : {}),
336
+ });
337
+ }
338
+ catch (cause) {
339
+ return Result.err(new StoreError({ operation: "list", cause, context: { prefix } }));
340
+ }
341
+ })());
342
+ }
343
+ status() {
344
+ return AsyncResult.from((async () => {
345
+ if (!this.#isCurrent())
346
+ return this.#stale("status");
347
+ try {
348
+ const status = await this.#store.status();
349
+ return Result.ok(storeStatusFromObjectStoreStatus(status, this.#options));
350
+ }
351
+ catch (cause) {
352
+ return Result.err(new StoreError({ operation: "status", cause }));
353
+ }
354
+ })());
355
+ }
356
+ async #putInternal(operation, key, body, options, previousRevision) {
357
+ try {
358
+ if (!this.#isCurrent())
359
+ return this.#stale(operation, key);
360
+ const metadata = metadataWithContentType(options);
361
+ if (body instanceof Uint8Array) {
362
+ if (this.#options.maxObjectBytes !== undefined &&
363
+ body.length > this.#options.maxObjectBytes) {
364
+ return Result.err(new StoreError({
365
+ operation,
366
+ context: {
367
+ key,
368
+ reason: "max_object_bytes_exceeded",
369
+ maxObjectBytes: this.#options.maxObjectBytes,
370
+ attemptedBytes: body.length,
371
+ },
372
+ }));
373
+ }
374
+ await this.#store.putBlob({ name: key, ...(metadata ? { metadata } : {}) }, body, previousRevision === undefined ? undefined : { previousRevision });
375
+ return Result.ok(undefined);
376
+ }
377
+ const limitedStream = enforceMaxObjectBytes(streamFromBody(body), this.#options.maxObjectBytes);
378
+ await this.#store.put({ name: key, ...(metadata ? { metadata } : {}) }, limitedStream, previousRevision === undefined ? undefined : { previousRevision });
379
+ return Result.ok(undefined);
380
+ }
381
+ catch (cause) {
382
+ return Result.err(cause instanceof StoreError
383
+ ? cause
384
+ : new StoreError({ operation, cause, context: { key } }));
385
+ }
386
+ }
387
+ #isCurrent() {
388
+ return this.#options.isCurrent?.() ?? true;
389
+ }
390
+ #stale(operation, key) {
391
+ return Result.err(new StoreError({
392
+ operation,
393
+ context: { ...(key ? { key } : {}), reason: "stale_binding" },
394
+ }));
395
+ }
396
+ }
397
+ function storeStatusFromObjectStoreStatus(status, options) {
398
+ return {
399
+ size: status.size,
400
+ sealed: status.sealed,
401
+ ttlMs: status.ttl > 0 ? Math.floor(status.ttl / 1_000_000) : 0,
402
+ ...(options.maxObjectBytes !== undefined
403
+ ? { maxObjectBytes: options.maxObjectBytes }
404
+ : {}),
405
+ ...(status.streamInfo.config.max_bytes > 0
406
+ ? { maxTotalBytes: status.streamInfo.config.max_bytes }
407
+ : {}),
408
+ };
409
+ }
410
+ export function bytesFromStoreStream(stream) {
411
+ return bytesFromStream(stream);
412
+ }
@@ -0,0 +1,20 @@
1
+ import { AsyncResult } from "@oatscenter/result";
2
+ import { StoreError } from "./errors/index.ts";
3
+ import type { StoreInfo } from "./store.ts";
4
+ type ObjectResultLike = {
5
+ data: ReadableStream<Uint8Array>;
6
+ error: Promise<unknown>;
7
+ };
8
+ type ObjectStoreLike = {
9
+ get(key: string): Promise<ObjectResultLike | null>;
10
+ getBlob(key: string): Promise<Uint8Array | null>;
11
+ };
12
+ export declare class TypedStoreEntry {
13
+ #private;
14
+ readonly key: string;
15
+ readonly info: StoreInfo;
16
+ constructor(store: ObjectStoreLike, info: StoreInfo);
17
+ stream(): AsyncResult<ReadableStream<Uint8Array>, StoreError>;
18
+ bytes(): AsyncResult<Uint8Array, StoreError>;
19
+ }
20
+ export {};
package/store_entry.js ADDED
@@ -0,0 +1,75 @@
1
+ import { AsyncResult, Result } from "@oatscenter/result";
2
+ import { StoreError } from "./errors/index.js";
3
+ export class TypedStoreEntry {
4
+ key;
5
+ info;
6
+ #store;
7
+ constructor(store, info) {
8
+ this.#store = store;
9
+ this.key = info.key;
10
+ this.info = info;
11
+ }
12
+ stream() {
13
+ return AsyncResult.from((async () => {
14
+ try {
15
+ const result = await this.#store.get(this.key);
16
+ if (result === null) {
17
+ return Result.err(new StoreError({
18
+ operation: "stream",
19
+ context: { key: this.key, reason: "not_found" },
20
+ }));
21
+ }
22
+ return Result.ok(streamWithErrorCheck(result));
23
+ }
24
+ catch (cause) {
25
+ return Result.err(new StoreError({
26
+ operation: "stream",
27
+ cause,
28
+ context: { key: this.key },
29
+ }));
30
+ }
31
+ })());
32
+ }
33
+ bytes() {
34
+ return AsyncResult.from((async () => {
35
+ try {
36
+ const bytes = await this.#store.getBlob(this.key);
37
+ if (bytes === null) {
38
+ return Result.err(new StoreError({
39
+ operation: "bytes",
40
+ context: { key: this.key, reason: "not_found" },
41
+ }));
42
+ }
43
+ return Result.ok(bytes);
44
+ }
45
+ catch (cause) {
46
+ return Result.err(new StoreError({
47
+ operation: "bytes",
48
+ cause,
49
+ context: { key: this.key },
50
+ }));
51
+ }
52
+ })());
53
+ }
54
+ }
55
+ function streamWithErrorCheck(result) {
56
+ const reader = result.data.getReader();
57
+ return new ReadableStream({
58
+ async pull(controller) {
59
+ const next = await reader.read();
60
+ if (next.done) {
61
+ const error = await result.error;
62
+ if (error) {
63
+ controller.error(error);
64
+ return;
65
+ }
66
+ controller.close();
67
+ return;
68
+ }
69
+ controller.enqueue(next.value);
70
+ },
71
+ async cancel(reason) {
72
+ await reader.cancel(reason);
73
+ },
74
+ });
75
+ }
package/tasks.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { AsyncResult, type BaseError } from "@oatscenter/result";
2
+ import { type LoggerLike } from "./globals.ts";
3
+ export declare class TrellisTasks {
4
+ #private;
5
+ constructor(opts: {
6
+ log?: LoggerLike;
7
+ });
8
+ add<E extends BaseError>(name: string, task: AsyncResult<void, E>): void;
9
+ wait(): AsyncResult<void, BaseError>;
10
+ }
package/tasks.js ADDED
@@ -0,0 +1,28 @@
1
+ import { AsyncResult, Result } from "@oatscenter/result";
2
+ import { logger } from "./globals.js";
3
+ export class TrellisTasks {
4
+ #tasks = {};
5
+ #log;
6
+ constructor(opts) {
7
+ this.#log = opts.log ?? logger;
8
+ }
9
+ /*
10
+ * Add a task to the task queue
11
+ */
12
+ add(name, task) {
13
+ if (name in this.#tasks) {
14
+ this.#log.error({ name }, "Task already running?");
15
+ throw new Error(`Task ${name} already running`);
16
+ }
17
+ this.#log.debug({ name }, "Added task");
18
+ this.#tasks[name] = task.then((r) => {
19
+ if (Result.isErr(r)) {
20
+ this.#log.error(r, "Task encountered a runtime error");
21
+ }
22
+ return r;
23
+ });
24
+ }
25
+ wait() {
26
+ return AsyncResult.any(Object.values(this.#tasks));
27
+ }
28
+ }
@@ -0,0 +1,8 @@
1
+ import { type Context, type Span } from "@opentelemetry/api";
2
+ export interface HeaderCarrier {
3
+ get(key: string): string | undefined;
4
+ set(key: string, value: string): void;
5
+ }
6
+ export declare function injectTraceContext(carrier: HeaderCarrier, span?: Span): void;
7
+ export declare function extractTraceContext(carrier: HeaderCarrier): Context;
8
+ export declare function createMapCarrier(): HeaderCarrier;
@@ -0,0 +1,20 @@
1
+ import { context, propagation, trace, } from "@opentelemetry/api";
2
+ export function injectTraceContext(carrier, span) {
3
+ const ctx = span ? trace.setSpan(context.active(), span) : context.active();
4
+ propagation.inject(ctx, carrier, {
5
+ set: (c, k, v) => c.set(k, v),
6
+ });
7
+ }
8
+ export function extractTraceContext(carrier) {
9
+ return propagation.extract(context.active(), carrier, {
10
+ get: (c, k) => c.get(k),
11
+ keys: () => [],
12
+ });
13
+ }
14
+ export function createMapCarrier() {
15
+ const map = new Map();
16
+ return {
17
+ get: (key) => map.get(key),
18
+ set: (key, value) => map.set(key, value),
19
+ };
20
+ }
@@ -0,0 +1,7 @@
1
+ import { type Context, context, type Span, SpanKind, SpanStatusCode, trace, type Tracer } from "@opentelemetry/api";
2
+ export declare function getTracer(scope?: string): Tracer;
3
+ export declare function getActiveSpan(): Span | undefined;
4
+ export declare function withSpan<T>(span: Span, fn: () => T): T;
5
+ export declare function withSpanAsync<T>(span: Span, fn: () => Promise<T>): Promise<T>;
6
+ export { context, SpanKind, SpanStatusCode, trace };
7
+ export type { Context, Span };
@@ -0,0 +1,14 @@
1
+ import { context, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
2
+ export function getTracer(scope = "@oatscenter/trellis/telemetry") {
3
+ return trace.getTracer(scope);
4
+ }
5
+ export function getActiveSpan() {
6
+ return trace.getActiveSpan();
7
+ }
8
+ export function withSpan(span, fn) {
9
+ return context.with(trace.setSpan(context.active(), span), fn);
10
+ }
11
+ export async function withSpanAsync(span, fn) {
12
+ return context.with(trace.setSpan(context.active(), span), fn);
13
+ }
14
+ export { context, SpanKind, SpanStatusCode, trace };
@@ -0,0 +1,2 @@
1
+ /** Reads an optional native environment variable without loading Node in browsers. */
2
+ export declare function getEnv(key: string): string | undefined;
@@ -0,0 +1,10 @@
1
+ /** Reads an optional native environment variable without loading Node in browsers. */
2
+ export function getEnv(key) {
3
+ const environmentGlobal = globalThis;
4
+ try {
5
+ return environmentGlobal.process?.env?.[key];
6
+ }
7
+ catch {
8
+ return undefined;
9
+ }
10
+ }