@zlayer/sdk 0.8.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.js ADDED
@@ -0,0 +1,1227 @@
1
+ /**
2
+ * ZLayer SDK for building WASM plugins in TypeScript.
3
+ *
4
+ * This module provides helper functions that wrap the generated WIT bindings
5
+ * for easier access to ZLayer host capabilities including configuration,
6
+ * key-value storage, logging, secrets, and metrics.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ // =============================================================================
11
+ // Version
12
+ // =============================================================================
13
+ /**
14
+ * The current version of the ZLayer SDK.
15
+ */
16
+ export const VERSION = '0.1.0';
17
+ // =============================================================================
18
+ // Error Classes
19
+ // =============================================================================
20
+ /**
21
+ * Error thrown when configuration operations fail.
22
+ */
23
+ export class ConfigError extends Error {
24
+ /** The configuration key that caused the error */
25
+ key;
26
+ constructor(key, message) {
27
+ super(message);
28
+ this.name = 'ConfigError';
29
+ this.key = key;
30
+ Object.setPrototypeOf(this, ConfigError.prototype);
31
+ }
32
+ }
33
+ /**
34
+ * Error thrown when key-value storage operations fail.
35
+ */
36
+ export class KVError extends Error {
37
+ /** The key that caused the error */
38
+ key;
39
+ /** The bucket where the error occurred */
40
+ bucket;
41
+ /** The error code from the host */
42
+ code;
43
+ constructor(bucket, key, code, message) {
44
+ super(message);
45
+ this.name = 'KVError';
46
+ this.bucket = bucket;
47
+ this.key = key;
48
+ this.code = code;
49
+ Object.setPrototypeOf(this, KVError.prototype);
50
+ }
51
+ }
52
+ /**
53
+ * Error codes for key-value operations.
54
+ */
55
+ export var KVErrorCode;
56
+ (function (KVErrorCode) {
57
+ /** Key not found */
58
+ KVErrorCode["NotFound"] = "not_found";
59
+ /** Value too large */
60
+ KVErrorCode["ValueTooLarge"] = "value_too_large";
61
+ /** Storage quota exceeded */
62
+ KVErrorCode["QuotaExceeded"] = "quota_exceeded";
63
+ /** Key format invalid */
64
+ KVErrorCode["InvalidKey"] = "invalid_key";
65
+ /** Generic storage error */
66
+ KVErrorCode["Storage"] = "storage";
67
+ })(KVErrorCode || (KVErrorCode = {}));
68
+ /**
69
+ * Error thrown when secret operations fail.
70
+ */
71
+ export class SecretError extends Error {
72
+ /** The secret name that caused the error */
73
+ secretName;
74
+ constructor(secretName, message) {
75
+ super(message);
76
+ this.name = 'SecretError';
77
+ this.secretName = secretName;
78
+ Object.setPrototypeOf(this, SecretError.prototype);
79
+ }
80
+ }
81
+ /**
82
+ * Error thrown when a required value is missing.
83
+ */
84
+ export class NotFoundError extends Error {
85
+ /** The identifier that was not found */
86
+ identifier;
87
+ constructor(identifier, message) {
88
+ super(message);
89
+ this.name = 'NotFoundError';
90
+ this.identifier = identifier;
91
+ Object.setPrototypeOf(this, NotFoundError.prototype);
92
+ }
93
+ }
94
+ // =============================================================================
95
+ // Log Level Enum
96
+ // =============================================================================
97
+ /**
98
+ * Log severity levels matching the WIT interface.
99
+ */
100
+ export var LogLevel;
101
+ (function (LogLevel) {
102
+ /** Finest-grained debugging information */
103
+ LogLevel[LogLevel["Trace"] = 0] = "Trace";
104
+ /** Debugging information */
105
+ LogLevel[LogLevel["Debug"] = 1] = "Debug";
106
+ /** Informational messages */
107
+ LogLevel[LogLevel["Info"] = 2] = "Info";
108
+ /** Warning messages */
109
+ LogLevel[LogLevel["Warn"] = 3] = "Warn";
110
+ /** Error messages */
111
+ LogLevel[LogLevel["Error"] = 4] = "Error";
112
+ })(LogLevel || (LogLevel = {}));
113
+ /**
114
+ * HTTP methods supported by the plugin system.
115
+ */
116
+ export var HttpMethod;
117
+ (function (HttpMethod) {
118
+ HttpMethod["Get"] = "GET";
119
+ HttpMethod["Post"] = "POST";
120
+ HttpMethod["Put"] = "PUT";
121
+ HttpMethod["Delete"] = "DELETE";
122
+ HttpMethod["Patch"] = "PATCH";
123
+ HttpMethod["Head"] = "HEAD";
124
+ HttpMethod["Options"] = "OPTIONS";
125
+ HttpMethod["Connect"] = "CONNECT";
126
+ HttpMethod["Trace"] = "TRACE";
127
+ })(HttpMethod || (HttpMethod = {}));
128
+ // =============================================================================
129
+ // Host Bindings Stub
130
+ // =============================================================================
131
+ /**
132
+ * Stub for host bindings. In actual WASM execution, these are replaced
133
+ * by the generated WIT bindings that call into the host.
134
+ */
135
+ const hostBindings = {
136
+ config: {
137
+ get: (_key) => {
138
+ throw new Error('Host bindings not available: config.get not implemented');
139
+ },
140
+ getRequired: (_key) => {
141
+ throw new Error('Host bindings not available: config.getRequired not implemented');
142
+ },
143
+ getMany: (_keys) => {
144
+ throw new Error('Host bindings not available: config.getMany not implemented');
145
+ },
146
+ getPrefix: (_prefix) => {
147
+ throw new Error('Host bindings not available: config.getPrefix not implemented');
148
+ },
149
+ exists: (_key) => {
150
+ throw new Error('Host bindings not available: config.exists not implemented');
151
+ },
152
+ getBool: (_key) => {
153
+ throw new Error('Host bindings not available: config.getBool not implemented');
154
+ },
155
+ getInt: (_key) => {
156
+ throw new Error('Host bindings not available: config.getInt not implemented');
157
+ },
158
+ getFloat: (_key) => {
159
+ throw new Error('Host bindings not available: config.getFloat not implemented');
160
+ },
161
+ },
162
+ keyvalue: {
163
+ get: (_key) => {
164
+ throw new Error('Host bindings not available: keyvalue.get not implemented');
165
+ },
166
+ getString: (_key) => {
167
+ throw new Error('Host bindings not available: keyvalue.getString not implemented');
168
+ },
169
+ set: (_key, _value) => {
170
+ throw new Error('Host bindings not available: keyvalue.set not implemented');
171
+ },
172
+ setString: (_key, _value) => {
173
+ throw new Error('Host bindings not available: keyvalue.setString not implemented');
174
+ },
175
+ setWithTtl: (_key, _value, _ttl) => {
176
+ throw new Error('Host bindings not available: keyvalue.setWithTtl not implemented');
177
+ },
178
+ delete: (_key) => {
179
+ throw new Error('Host bindings not available: keyvalue.delete not implemented');
180
+ },
181
+ exists: (_key) => {
182
+ throw new Error('Host bindings not available: keyvalue.exists not implemented');
183
+ },
184
+ listKeys: (_prefix) => {
185
+ throw new Error('Host bindings not available: keyvalue.listKeys not implemented');
186
+ },
187
+ increment: (_key, _delta) => {
188
+ throw new Error('Host bindings not available: keyvalue.increment not implemented');
189
+ },
190
+ compareAndSwap: (_key, _expected, _newValue) => {
191
+ throw new Error('Host bindings not available: keyvalue.compareAndSwap not implemented');
192
+ },
193
+ },
194
+ logging: {
195
+ log: (_level, _message) => {
196
+ throw new Error('Host bindings not available: logging.log not implemented');
197
+ },
198
+ logStructured: (_level, _message, _fields) => {
199
+ throw new Error('Host bindings not available: logging.logStructured not implemented');
200
+ },
201
+ trace: (_message) => {
202
+ throw new Error('Host bindings not available: logging.trace not implemented');
203
+ },
204
+ debug: (_message) => {
205
+ throw new Error('Host bindings not available: logging.debug not implemented');
206
+ },
207
+ info: (_message) => {
208
+ throw new Error('Host bindings not available: logging.info not implemented');
209
+ },
210
+ warn: (_message) => {
211
+ throw new Error('Host bindings not available: logging.warn not implemented');
212
+ },
213
+ error: (_message) => {
214
+ throw new Error('Host bindings not available: logging.error not implemented');
215
+ },
216
+ isEnabled: (_level) => {
217
+ throw new Error('Host bindings not available: logging.isEnabled not implemented');
218
+ },
219
+ },
220
+ secrets: {
221
+ get: (_name) => {
222
+ throw new Error('Host bindings not available: secrets.get not implemented');
223
+ },
224
+ getRequired: (_name) => {
225
+ throw new Error('Host bindings not available: secrets.getRequired not implemented');
226
+ },
227
+ exists: (_name) => {
228
+ throw new Error('Host bindings not available: secrets.exists not implemented');
229
+ },
230
+ listNames: () => {
231
+ throw new Error('Host bindings not available: secrets.listNames not implemented');
232
+ },
233
+ },
234
+ metrics: {
235
+ counterInc: (_name, _value) => {
236
+ throw new Error('Host bindings not available: metrics.counterInc not implemented');
237
+ },
238
+ counterIncLabeled: (_name, _value, _labels) => {
239
+ throw new Error('Host bindings not available: metrics.counterIncLabeled not implemented');
240
+ },
241
+ gaugeSet: (_name, _value) => {
242
+ throw new Error('Host bindings not available: metrics.gaugeSet not implemented');
243
+ },
244
+ gaugeSetLabeled: (_name, _value, _labels) => {
245
+ throw new Error('Host bindings not available: metrics.gaugeSetLabeled not implemented');
246
+ },
247
+ gaugeAdd: (_name, _delta) => {
248
+ throw new Error('Host bindings not available: metrics.gaugeAdd not implemented');
249
+ },
250
+ histogramObserve: (_name, _value) => {
251
+ throw new Error('Host bindings not available: metrics.histogramObserve not implemented');
252
+ },
253
+ histogramObserveLabeled: (_name, _value, _labels) => {
254
+ throw new Error('Host bindings not available: metrics.histogramObserveLabeled not implemented');
255
+ },
256
+ recordDuration: (_name, _durationNs) => {
257
+ throw new Error('Host bindings not available: metrics.recordDuration not implemented');
258
+ },
259
+ recordDurationLabeled: (_name, _durationNs, _labels) => {
260
+ throw new Error('Host bindings not available: metrics.recordDurationLabeled not implemented');
261
+ },
262
+ },
263
+ };
264
+ // =============================================================================
265
+ // Configuration Helpers
266
+ // =============================================================================
267
+ /**
268
+ * Get a configuration value by key.
269
+ *
270
+ * @param key - The configuration key to retrieve
271
+ * @returns The configuration value, or undefined if not found
272
+ *
273
+ * @example
274
+ * ```typescript
275
+ * const dbHost = getConfig('database.host');
276
+ * if (dbHost) {
277
+ * console.log(`Database host: ${dbHost}`);
278
+ * }
279
+ * ```
280
+ */
281
+ export function getConfig(key) {
282
+ return hostBindings.config.get(key);
283
+ }
284
+ /**
285
+ * Get a required configuration value by key.
286
+ * Throws ConfigError if the key does not exist.
287
+ *
288
+ * @param key - The configuration key to retrieve
289
+ * @returns The configuration value
290
+ * @throws {ConfigError} If the configuration key does not exist
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * try {
295
+ * const apiKey = getConfigRequired('api.key');
296
+ * } catch (e) {
297
+ * if (e instanceof ConfigError) {
298
+ * log.error(`Missing config: ${e.key}`);
299
+ * }
300
+ * }
301
+ * ```
302
+ */
303
+ export function getConfigRequired(key) {
304
+ const value = hostBindings.config.get(key);
305
+ if (value === undefined) {
306
+ throw new ConfigError(key, `Required configuration key '${key}' not found`);
307
+ }
308
+ return value;
309
+ }
310
+ /**
311
+ * Get a configuration value as a boolean.
312
+ * Recognizes: "true", "false", "1", "0", "yes", "no" (case-insensitive).
313
+ *
314
+ * @param key - The configuration key to retrieve
315
+ * @returns The boolean value, or undefined if not found or not parseable
316
+ *
317
+ * @example
318
+ * ```typescript
319
+ * const debugMode = getConfigBool('debug.enabled') ?? false;
320
+ * ```
321
+ */
322
+ export function getConfigBool(key) {
323
+ return hostBindings.config.getBool(key);
324
+ }
325
+ /**
326
+ * Get a configuration value as an integer.
327
+ *
328
+ * @param key - The configuration key to retrieve
329
+ * @returns The integer value, or undefined if not found or not parseable
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * const maxRetries = getConfigInt('http.max_retries') ?? 3;
334
+ * ```
335
+ */
336
+ export function getConfigInt(key) {
337
+ const value = hostBindings.config.getInt(key);
338
+ if (value === undefined) {
339
+ return undefined;
340
+ }
341
+ return Number(value);
342
+ }
343
+ /**
344
+ * Get a configuration value as a float.
345
+ *
346
+ * @param key - The configuration key to retrieve
347
+ * @returns The float value, or undefined if not found or not parseable
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * const timeout = getConfigFloat('http.timeout_seconds') ?? 30.0;
352
+ * ```
353
+ */
354
+ export function getConfigFloat(key) {
355
+ return hostBindings.config.getFloat(key);
356
+ }
357
+ /**
358
+ * Get multiple configuration values at once.
359
+ *
360
+ * @param keys - The configuration keys to retrieve
361
+ * @returns A Map of key to value for keys that exist
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const config = getConfigMany(['db.host', 'db.port', 'db.name']);
366
+ * const host = config.get('db.host') ?? 'localhost';
367
+ * ```
368
+ */
369
+ export function getConfigMany(keys) {
370
+ const pairs = hostBindings.config.getMany(keys);
371
+ return new Map(pairs);
372
+ }
373
+ /**
374
+ * Get all configuration keys with a given prefix.
375
+ *
376
+ * @param prefix - The prefix to match (e.g., "database.")
377
+ * @returns A Map of key to value for matching keys
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * const dbConfig = getConfigPrefix('database.');
382
+ * // Returns: Map { 'database.host' => 'localhost', 'database.port' => '5432' }
383
+ * ```
384
+ */
385
+ export function getConfigPrefix(prefix) {
386
+ const pairs = hostBindings.config.getPrefix(prefix);
387
+ return new Map(pairs);
388
+ }
389
+ /**
390
+ * Check if a configuration key exists.
391
+ *
392
+ * @param key - The configuration key to check
393
+ * @returns true if the key exists, false otherwise
394
+ */
395
+ export function configExists(key) {
396
+ return hostBindings.config.exists(key);
397
+ }
398
+ /**
399
+ * Get all configuration as a JSON string.
400
+ * Useful for debugging or serialization.
401
+ *
402
+ * @returns JSON string of all configuration
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * log.debug(`All config: ${getAllConfig()}`);
407
+ * ```
408
+ */
409
+ export function getAllConfig() {
410
+ const pairs = hostBindings.config.getPrefix('');
411
+ const obj = {};
412
+ for (const [key, value] of pairs) {
413
+ obj[key] = value;
414
+ }
415
+ return JSON.stringify(obj);
416
+ }
417
+ // =============================================================================
418
+ // Key-Value Storage Helpers
419
+ // =============================================================================
420
+ /**
421
+ * Construct a namespaced key from bucket and key.
422
+ *
423
+ * @param bucket - The bucket/namespace
424
+ * @param key - The key within the bucket
425
+ * @returns The full namespaced key
426
+ */
427
+ function makeKey(bucket, key) {
428
+ return `${bucket}:${key}`;
429
+ }
430
+ /**
431
+ * Get a value from key-value storage as bytes.
432
+ *
433
+ * @param bucket - The bucket/namespace
434
+ * @param key - The key to retrieve
435
+ * @returns The value as bytes, or undefined if not found
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * const data = kvGet('sessions', sessionId);
440
+ * if (data) {
441
+ * const session = JSON.parse(new TextDecoder().decode(data));
442
+ * }
443
+ * ```
444
+ */
445
+ export function kvGet(bucket, key) {
446
+ return hostBindings.keyvalue.get(makeKey(bucket, key));
447
+ }
448
+ /**
449
+ * Get a value from key-value storage as a string.
450
+ *
451
+ * @param bucket - The bucket/namespace
452
+ * @param key - The key to retrieve
453
+ * @returns The value as a string, or undefined if not found
454
+ *
455
+ * @example
456
+ * ```typescript
457
+ * const username = kvGetString('users', `user:${userId}:name`);
458
+ * ```
459
+ */
460
+ export function kvGetString(bucket, key) {
461
+ return hostBindings.keyvalue.getString(makeKey(bucket, key));
462
+ }
463
+ /**
464
+ * Get a value from key-value storage as JSON.
465
+ *
466
+ * @param bucket - The bucket/namespace
467
+ * @param key - The key to retrieve
468
+ * @returns The parsed JSON value, or undefined if not found
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * interface User { name: string; email: string; }
473
+ * const user = kvGetJson<User>('users', userId);
474
+ * ```
475
+ */
476
+ export function kvGetJson(bucket, key) {
477
+ const value = kvGetString(bucket, key);
478
+ if (value === undefined) {
479
+ return undefined;
480
+ }
481
+ return JSON.parse(value);
482
+ }
483
+ /**
484
+ * Set a value in key-value storage as bytes.
485
+ *
486
+ * @param bucket - The bucket/namespace
487
+ * @param key - The key to set
488
+ * @param value - The value as bytes
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * const encoder = new TextEncoder();
493
+ * kvSet('sessions', sessionId, encoder.encode(JSON.stringify(session)));
494
+ * ```
495
+ */
496
+ export function kvSet(bucket, key, value) {
497
+ hostBindings.keyvalue.set(makeKey(bucket, key), value);
498
+ }
499
+ /**
500
+ * Set a value in key-value storage as a string.
501
+ *
502
+ * @param bucket - The bucket/namespace
503
+ * @param key - The key to set
504
+ * @param value - The value as a string
505
+ *
506
+ * @example
507
+ * ```typescript
508
+ * kvSetString('users', `user:${userId}:name`, 'John Doe');
509
+ * ```
510
+ */
511
+ export function kvSetString(bucket, key, value) {
512
+ hostBindings.keyvalue.setString(makeKey(bucket, key), value);
513
+ }
514
+ /**
515
+ * Set a value in key-value storage as JSON.
516
+ *
517
+ * @param bucket - The bucket/namespace
518
+ * @param key - The key to set
519
+ * @param value - The value to serialize as JSON
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * kvSetJson('users', userId, { name: 'John', email: 'john@example.com' });
524
+ * ```
525
+ */
526
+ export function kvSetJson(bucket, key, value) {
527
+ kvSetString(bucket, key, JSON.stringify(value));
528
+ }
529
+ /**
530
+ * Set a value in key-value storage with a TTL (time-to-live).
531
+ *
532
+ * @param bucket - The bucket/namespace
533
+ * @param key - The key to set
534
+ * @param value - The value as bytes
535
+ * @param ttlMs - Time-to-live in milliseconds
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * // Cache for 5 minutes
540
+ * kvSetWithTtl('cache', cacheKey, data, 5 * 60 * 1000);
541
+ * ```
542
+ */
543
+ export function kvSetWithTtl(bucket, key, value, ttlMs) {
544
+ const ttlNs = BigInt(ttlMs) * BigInt(1_000_000);
545
+ hostBindings.keyvalue.setWithTtl(makeKey(bucket, key), value, ttlNs);
546
+ }
547
+ /**
548
+ * Set a string value in key-value storage with a TTL.
549
+ *
550
+ * @param bucket - The bucket/namespace
551
+ * @param key - The key to set
552
+ * @param value - The value as a string
553
+ * @param ttlMs - Time-to-live in milliseconds
554
+ */
555
+ export function kvSetStringWithTtl(bucket, key, value, ttlMs) {
556
+ const encoder = new TextEncoder();
557
+ kvSetWithTtl(bucket, key, encoder.encode(value), ttlMs);
558
+ }
559
+ /**
560
+ * Delete a key from key-value storage.
561
+ *
562
+ * @param bucket - The bucket/namespace
563
+ * @param key - The key to delete
564
+ * @returns true if the key was deleted, false if it didn't exist
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * kvDelete('sessions', sessionId);
569
+ * ```
570
+ */
571
+ export function kvDelete(bucket, key) {
572
+ return hostBindings.keyvalue.delete(makeKey(bucket, key));
573
+ }
574
+ /**
575
+ * List all keys in a bucket with an optional prefix.
576
+ *
577
+ * @param bucket - The bucket/namespace
578
+ * @param prefix - Optional prefix to filter keys
579
+ * @returns Array of keys matching the prefix
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * const userKeys = kvKeys('users', 'user:');
584
+ * // Returns: ['user:1', 'user:2', 'user:3']
585
+ * ```
586
+ */
587
+ export function kvKeys(bucket, prefix = '') {
588
+ const fullPrefix = makeKey(bucket, prefix);
589
+ const keys = hostBindings.keyvalue.listKeys(fullPrefix);
590
+ // Strip the bucket prefix from returned keys
591
+ const bucketPrefix = `${bucket}:`;
592
+ return keys.map((k) => k.startsWith(bucketPrefix) ? k.slice(bucketPrefix.length) : k);
593
+ }
594
+ /**
595
+ * Check if a key exists in key-value storage.
596
+ *
597
+ * @param bucket - The bucket/namespace
598
+ * @param key - The key to check
599
+ * @returns true if the key exists, false otherwise
600
+ */
601
+ export function kvExists(bucket, key) {
602
+ return hostBindings.keyvalue.exists(makeKey(bucket, key));
603
+ }
604
+ /**
605
+ * Atomically increment a numeric value in key-value storage.
606
+ *
607
+ * @param bucket - The bucket/namespace
608
+ * @param key - The key to increment
609
+ * @param delta - The amount to increment by (default: 1)
610
+ * @returns The new value after incrementing
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * const newCount = kvIncrement('counters', 'page_views', 1);
615
+ * ```
616
+ */
617
+ export function kvIncrement(bucket, key, delta = 1) {
618
+ return hostBindings.keyvalue.increment(makeKey(bucket, key), BigInt(delta));
619
+ }
620
+ /**
621
+ * Atomically compare and swap a value in key-value storage.
622
+ *
623
+ * @param bucket - The bucket/namespace
624
+ * @param key - The key to update
625
+ * @param expected - The expected current value (undefined if key shouldn't exist)
626
+ * @param newValue - The new value to set if expected matches
627
+ * @returns true if the swap succeeded, false if current value didn't match
628
+ *
629
+ * @example
630
+ * ```typescript
631
+ * // Optimistic locking
632
+ * const current = kvGet('locks', 'resource');
633
+ * const swapped = kvCompareAndSwap('locks', 'resource', current, newLockValue);
634
+ * if (!swapped) {
635
+ * throw new Error('Resource was modified by another process');
636
+ * }
637
+ * ```
638
+ */
639
+ export function kvCompareAndSwap(bucket, key, expected, newValue) {
640
+ return hostBindings.keyvalue.compareAndSwap(makeKey(bucket, key), expected, newValue);
641
+ }
642
+ // =============================================================================
643
+ // Logging Helpers
644
+ // =============================================================================
645
+ /**
646
+ * Logging utilities for emitting structured logs to the host.
647
+ */
648
+ export const log = {
649
+ /**
650
+ * Emit a trace-level log message.
651
+ * Use for finest-grained debugging information.
652
+ *
653
+ * @param msg - The log message
654
+ */
655
+ trace: (msg) => {
656
+ hostBindings.logging.trace(msg);
657
+ },
658
+ /**
659
+ * Emit a debug-level log message.
660
+ * Use for debugging information.
661
+ *
662
+ * @param msg - The log message
663
+ */
664
+ debug: (msg) => {
665
+ hostBindings.logging.debug(msg);
666
+ },
667
+ /**
668
+ * Emit an info-level log message.
669
+ * Use for informational messages about normal operation.
670
+ *
671
+ * @param msg - The log message
672
+ */
673
+ info: (msg) => {
674
+ hostBindings.logging.info(msg);
675
+ },
676
+ /**
677
+ * Emit a warn-level log message.
678
+ * Use for warning messages about potential issues.
679
+ *
680
+ * @param msg - The log message
681
+ */
682
+ warn: (msg) => {
683
+ hostBindings.logging.warn(msg);
684
+ },
685
+ /**
686
+ * Emit an error-level log message.
687
+ * Use for error messages.
688
+ *
689
+ * @param msg - The log message
690
+ */
691
+ error: (msg) => {
692
+ hostBindings.logging.error(msg);
693
+ },
694
+ /**
695
+ * Emit a log message at the specified level.
696
+ *
697
+ * @param level - The log level
698
+ * @param msg - The log message
699
+ */
700
+ log: (level, msg) => {
701
+ hostBindings.logging.log(level, msg);
702
+ },
703
+ /**
704
+ * Emit a structured log message with key-value fields.
705
+ *
706
+ * @param level - The log level
707
+ * @param msg - The log message
708
+ * @param fields - Additional structured fields
709
+ *
710
+ * @example
711
+ * ```typescript
712
+ * log.structured(LogLevel.Info, 'Request processed', {
713
+ * requestId: '123',
714
+ * duration: '45ms',
715
+ * status: '200',
716
+ * });
717
+ * ```
718
+ */
719
+ structured: (level, msg, fields) => {
720
+ const kvFields = Object.entries(fields).map(([key, value]) => ({
721
+ key,
722
+ value,
723
+ }));
724
+ hostBindings.logging.logStructured(level, msg, kvFields);
725
+ },
726
+ /**
727
+ * Check if a log level is enabled.
728
+ * Useful for avoiding expensive log construction when the level is disabled.
729
+ *
730
+ * @param level - The log level to check
731
+ * @returns true if the level is enabled
732
+ *
733
+ * @example
734
+ * ```typescript
735
+ * if (log.isEnabled(LogLevel.Debug)) {
736
+ * log.debug(`Complex data: ${JSON.stringify(largeObject)}`);
737
+ * }
738
+ * ```
739
+ */
740
+ isEnabled: (level) => {
741
+ return hostBindings.logging.isEnabled(level);
742
+ },
743
+ };
744
+ // =============================================================================
745
+ // Secrets Helpers
746
+ // =============================================================================
747
+ /**
748
+ * Get a secret by name.
749
+ *
750
+ * @param name - The secret name
751
+ * @returns The secret value, or undefined if not found
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * const apiKey = getSecret('external_api_key');
756
+ * ```
757
+ */
758
+ export function getSecret(name) {
759
+ return hostBindings.secrets.get(name);
760
+ }
761
+ /**
762
+ * Get a required secret by name.
763
+ * Throws SecretError if the secret does not exist.
764
+ *
765
+ * @param name - The secret name
766
+ * @returns The secret value
767
+ * @throws {SecretError} If the secret does not exist
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * const dbPassword = getSecretRequired('database_password');
772
+ * ```
773
+ */
774
+ export function getSecretRequired(name) {
775
+ const value = hostBindings.secrets.get(name);
776
+ if (value === undefined) {
777
+ throw new SecretError(name, `Required secret '${name}' not found`);
778
+ }
779
+ return value;
780
+ }
781
+ /**
782
+ * Check if a secret exists.
783
+ *
784
+ * @param name - The secret name
785
+ * @returns true if the secret exists, false otherwise
786
+ */
787
+ export function secretExists(name) {
788
+ return hostBindings.secrets.exists(name);
789
+ }
790
+ /**
791
+ * List all available secret names (not values).
792
+ * Useful for diagnostics without exposing sensitive data.
793
+ *
794
+ * @returns Array of secret names
795
+ */
796
+ export function listSecretNames() {
797
+ return hostBindings.secrets.listNames();
798
+ }
799
+ // =============================================================================
800
+ // Metrics Helpers
801
+ // =============================================================================
802
+ /**
803
+ * Metrics utilities for emitting observability data to the host.
804
+ */
805
+ export const metrics = {
806
+ /**
807
+ * Increment a counter metric.
808
+ *
809
+ * @param name - The metric name
810
+ * @param value - The amount to increment by (default: 1)
811
+ *
812
+ * @example
813
+ * ```typescript
814
+ * metrics.counterInc('requests_total');
815
+ * metrics.counterInc('bytes_processed', byteCount);
816
+ * ```
817
+ */
818
+ counterInc: (name, value = 1) => {
819
+ hostBindings.metrics.counterInc(name, BigInt(value));
820
+ },
821
+ /**
822
+ * Increment a counter metric with labels.
823
+ *
824
+ * @param name - The metric name
825
+ * @param value - The amount to increment by
826
+ * @param labels - The metric labels
827
+ *
828
+ * @example
829
+ * ```typescript
830
+ * metrics.counterIncLabeled('http_requests_total', 1, {
831
+ * method: 'GET',
832
+ * path: '/api/users',
833
+ * status: '200',
834
+ * });
835
+ * ```
836
+ */
837
+ counterIncLabeled: (name, value, labels) => {
838
+ const kvLabels = Object.entries(labels).map(([key, val]) => ({
839
+ key,
840
+ value: val,
841
+ }));
842
+ hostBindings.metrics.counterIncLabeled(name, BigInt(value), kvLabels);
843
+ },
844
+ /**
845
+ * Set a gauge metric to a value.
846
+ *
847
+ * @param name - The metric name
848
+ * @param value - The value to set
849
+ *
850
+ * @example
851
+ * ```typescript
852
+ * metrics.gaugeSet('active_connections', connectionCount);
853
+ * ```
854
+ */
855
+ gaugeSet: (name, value) => {
856
+ hostBindings.metrics.gaugeSet(name, value);
857
+ },
858
+ /**
859
+ * Set a gauge metric with labels.
860
+ *
861
+ * @param name - The metric name
862
+ * @param value - The value to set
863
+ * @param labels - The metric labels
864
+ */
865
+ gaugeSetLabeled: (name, value, labels) => {
866
+ const kvLabels = Object.entries(labels).map(([key, val]) => ({
867
+ key,
868
+ value: val,
869
+ }));
870
+ hostBindings.metrics.gaugeSetLabeled(name, value, kvLabels);
871
+ },
872
+ /**
873
+ * Add to a gauge value (can be negative).
874
+ *
875
+ * @param name - The metric name
876
+ * @param delta - The amount to add (can be negative)
877
+ */
878
+ gaugeAdd: (name, delta) => {
879
+ hostBindings.metrics.gaugeAdd(name, delta);
880
+ },
881
+ /**
882
+ * Record a histogram observation.
883
+ *
884
+ * @param name - The metric name
885
+ * @param value - The observed value
886
+ *
887
+ * @example
888
+ * ```typescript
889
+ * metrics.histogramObserve('request_duration_seconds', 0.045);
890
+ * ```
891
+ */
892
+ histogramObserve: (name, value) => {
893
+ hostBindings.metrics.histogramObserve(name, value);
894
+ },
895
+ /**
896
+ * Record a histogram observation with labels.
897
+ *
898
+ * @param name - The metric name
899
+ * @param value - The observed value
900
+ * @param labels - The metric labels
901
+ */
902
+ histogramObserveLabeled: (name, value, labels) => {
903
+ const kvLabels = Object.entries(labels).map(([key, val]) => ({
904
+ key,
905
+ value: val,
906
+ }));
907
+ hostBindings.metrics.histogramObserveLabeled(name, value, kvLabels);
908
+ },
909
+ /**
910
+ * Record request duration in milliseconds.
911
+ * Convenience method that converts to nanoseconds.
912
+ *
913
+ * @param name - The metric name
914
+ * @param durationMs - Duration in milliseconds
915
+ */
916
+ recordDuration: (name, durationMs) => {
917
+ const durationNs = BigInt(Math.floor(durationMs * 1_000_000));
918
+ hostBindings.metrics.recordDuration(name, durationNs);
919
+ },
920
+ /**
921
+ * Record request duration with labels.
922
+ *
923
+ * @param name - The metric name
924
+ * @param durationMs - Duration in milliseconds
925
+ * @param labels - The metric labels
926
+ */
927
+ recordDurationLabeled: (name, durationMs, labels) => {
928
+ const durationNs = BigInt(Math.floor(durationMs * 1_000_000));
929
+ const kvLabels = Object.entries(labels).map(([key, val]) => ({
930
+ key,
931
+ value: val,
932
+ }));
933
+ hostBindings.metrics.recordDurationLabeled(name, durationNs, kvLabels);
934
+ },
935
+ };
936
+ // =============================================================================
937
+ // Response Helpers
938
+ // =============================================================================
939
+ /**
940
+ * Create a successful response with JSON body.
941
+ *
942
+ * @param data - The data to serialize as JSON
943
+ * @param status - HTTP status code (default: 200)
944
+ * @param headers - Additional headers
945
+ * @returns A PluginResponse
946
+ *
947
+ * @example
948
+ * ```typescript
949
+ * return jsonResponse({ users: ['alice', 'bob'] });
950
+ * return jsonResponse({ error: 'Not found' }, 404);
951
+ * ```
952
+ */
953
+ export function jsonResponse(data, status = 200, headers = {}) {
954
+ const body = JSON.stringify(data);
955
+ const encoder = new TextEncoder();
956
+ const responseHeaders = [
957
+ { key: 'Content-Type', value: 'application/json' },
958
+ ...Object.entries(headers).map(([key, value]) => ({ key, value })),
959
+ ];
960
+ return {
961
+ status,
962
+ headers: responseHeaders,
963
+ body: encoder.encode(body),
964
+ };
965
+ }
966
+ /**
967
+ * Create a text response.
968
+ *
969
+ * @param text - The response text
970
+ * @param status - HTTP status code (default: 200)
971
+ * @param contentType - Content type (default: text/plain)
972
+ * @returns A PluginResponse
973
+ */
974
+ export function textResponse(text, status = 200, contentType = 'text/plain') {
975
+ const encoder = new TextEncoder();
976
+ return {
977
+ status,
978
+ headers: [{ key: 'Content-Type', value: contentType }],
979
+ body: encoder.encode(text),
980
+ };
981
+ }
982
+ /**
983
+ * Create an HTML response.
984
+ *
985
+ * @param html - The HTML content
986
+ * @param status - HTTP status code (default: 200)
987
+ * @returns A PluginResponse
988
+ */
989
+ export function htmlResponse(html, status = 200) {
990
+ return textResponse(html, status, 'text/html; charset=utf-8');
991
+ }
992
+ /**
993
+ * Create a binary response.
994
+ *
995
+ * @param data - The binary data
996
+ * @param contentType - Content type
997
+ * @param status - HTTP status code (default: 200)
998
+ * @returns A PluginResponse
999
+ */
1000
+ export function binaryResponse(data, contentType, status = 200) {
1001
+ return {
1002
+ status,
1003
+ headers: [{ key: 'Content-Type', value: contentType }],
1004
+ body: data,
1005
+ };
1006
+ }
1007
+ /**
1008
+ * Create an error response.
1009
+ *
1010
+ * @param message - The error message
1011
+ * @param status - HTTP status code (default: 500)
1012
+ * @returns A HandleResult with error response
1013
+ */
1014
+ export function errorResponse(message, status = 500) {
1015
+ return {
1016
+ type: 'response',
1017
+ response: jsonResponse({ error: message }, status),
1018
+ };
1019
+ }
1020
+ /**
1021
+ * Create a redirect response.
1022
+ *
1023
+ * @param location - The URL to redirect to
1024
+ * @param status - HTTP status code (default: 302)
1025
+ * @returns A PluginResponse
1026
+ */
1027
+ export function redirectResponse(location, status = 302) {
1028
+ return {
1029
+ status,
1030
+ headers: [{ key: 'Location', value: location }],
1031
+ body: new Uint8Array(0),
1032
+ };
1033
+ }
1034
+ /**
1035
+ * Create a pass-through result.
1036
+ * Use this when the plugin doesn't want to handle the request.
1037
+ *
1038
+ * @returns A HandleResult indicating pass-through
1039
+ */
1040
+ export function passThrough() {
1041
+ return { type: 'passThrough' };
1042
+ }
1043
+ // =============================================================================
1044
+ // Request Helpers
1045
+ // =============================================================================
1046
+ /**
1047
+ * Get a header value from a request (case-insensitive).
1048
+ *
1049
+ * @param request - The plugin request
1050
+ * @param name - The header name
1051
+ * @returns The header value, or undefined if not found
1052
+ */
1053
+ export function getHeader(request, name) {
1054
+ const lowerName = name.toLowerCase();
1055
+ const header = request.headers.find((h) => h.key.toLowerCase() === lowerName);
1056
+ return header?.value;
1057
+ }
1058
+ /**
1059
+ * Get all values for a header (case-insensitive).
1060
+ *
1061
+ * @param request - The plugin request
1062
+ * @param name - The header name
1063
+ * @returns Array of header values
1064
+ */
1065
+ export function getHeaders(request, name) {
1066
+ const lowerName = name.toLowerCase();
1067
+ return request.headers
1068
+ .filter((h) => h.key.toLowerCase() === lowerName)
1069
+ .map((h) => h.value);
1070
+ }
1071
+ /**
1072
+ * Parse the request body as JSON.
1073
+ *
1074
+ * @param request - The plugin request
1075
+ * @returns The parsed JSON body
1076
+ */
1077
+ export function parseJsonBody(request) {
1078
+ const decoder = new TextDecoder();
1079
+ const text = decoder.decode(request.body);
1080
+ return JSON.parse(text);
1081
+ }
1082
+ /**
1083
+ * Get the request body as a string.
1084
+ *
1085
+ * @param request - The plugin request
1086
+ * @returns The body as a string
1087
+ */
1088
+ export function getBodyString(request) {
1089
+ const decoder = new TextDecoder();
1090
+ return decoder.decode(request.body);
1091
+ }
1092
+ /**
1093
+ * Parse query string into a Map.
1094
+ *
1095
+ * @param request - The plugin request
1096
+ * @returns Map of query parameters
1097
+ */
1098
+ export function parseQuery(request) {
1099
+ const params = new Map();
1100
+ if (!request.query) {
1101
+ return params;
1102
+ }
1103
+ const pairs = request.query.split('&');
1104
+ for (const pair of pairs) {
1105
+ const [key, value] = pair.split('=');
1106
+ if (key) {
1107
+ params.set(decodeURIComponent(key), decodeURIComponent(value ?? ''));
1108
+ }
1109
+ }
1110
+ return params;
1111
+ }
1112
+ /**
1113
+ * Get a context value from a request.
1114
+ *
1115
+ * @param request - The plugin request
1116
+ * @param key - The context key
1117
+ * @returns The context value, or undefined if not found
1118
+ */
1119
+ export function getContext(request, key) {
1120
+ const ctx = request.context.find((c) => c.key === key);
1121
+ return ctx?.value;
1122
+ }
1123
+ // =============================================================================
1124
+ // Timing Helpers
1125
+ // =============================================================================
1126
+ /**
1127
+ * Measure the duration of an async operation and record it as a metric.
1128
+ *
1129
+ * @param name - The metric name
1130
+ * @param fn - The async function to measure
1131
+ * @returns The result of the function
1132
+ *
1133
+ * @example
1134
+ * ```typescript
1135
+ * const result = await timed('database_query', async () => {
1136
+ * return await db.query('SELECT * FROM users');
1137
+ * });
1138
+ * ```
1139
+ */
1140
+ export async function timed(name, fn) {
1141
+ const start = Date.now();
1142
+ try {
1143
+ return await fn();
1144
+ }
1145
+ finally {
1146
+ const duration = Date.now() - start;
1147
+ metrics.recordDuration(name, duration);
1148
+ }
1149
+ }
1150
+ /**
1151
+ * Measure the duration of a sync operation and record it as a metric.
1152
+ *
1153
+ * @param name - The metric name
1154
+ * @param fn - The function to measure
1155
+ * @returns The result of the function
1156
+ */
1157
+ export function timedSync(name, fn) {
1158
+ const start = Date.now();
1159
+ try {
1160
+ return fn();
1161
+ }
1162
+ finally {
1163
+ const duration = Date.now() - start;
1164
+ metrics.recordDuration(name, duration);
1165
+ }
1166
+ }
1167
+ // =============================================================================
1168
+ // Version Helpers
1169
+ // =============================================================================
1170
+ /**
1171
+ * Create a Version object from a semver string.
1172
+ *
1173
+ * @param semver - The semver string (e.g., "1.2.3" or "1.2.3-beta.1")
1174
+ * @returns A Version object
1175
+ */
1176
+ export function parseVersion(semver) {
1177
+ const [versionPart, preRelease] = semver.split('-', 2);
1178
+ const [major, minor, patch] = versionPart.split('.').map(Number);
1179
+ return {
1180
+ major: major ?? 0,
1181
+ minor: minor ?? 0,
1182
+ patch: patch ?? 0,
1183
+ preRelease,
1184
+ };
1185
+ }
1186
+ /**
1187
+ * Format a Version object as a semver string.
1188
+ *
1189
+ * @param version - The Version object
1190
+ * @returns The semver string
1191
+ */
1192
+ export function formatVersion(version) {
1193
+ const base = `${version.major}.${version.minor}.${version.patch}`;
1194
+ return version.preRelease ? `${base}-${version.preRelease}` : base;
1195
+ }
1196
+ // =============================================================================
1197
+ // Plugin Registration Helper
1198
+ // =============================================================================
1199
+ /**
1200
+ * Register a plugin implementation.
1201
+ * This is a convenience function for setting up plugin exports.
1202
+ *
1203
+ * @param plugin - The plugin implementation
1204
+ * @returns The plugin for export
1205
+ *
1206
+ * @example
1207
+ * ```typescript
1208
+ * export default registerPlugin({
1209
+ * info() {
1210
+ * return {
1211
+ * id: 'my-org:my-plugin',
1212
+ * name: 'My Plugin',
1213
+ * version: { major: 1, minor: 0, patch: 0 },
1214
+ * description: 'A sample plugin',
1215
+ * author: 'My Org',
1216
+ * };
1217
+ * },
1218
+ * handle(request) {
1219
+ * return { type: 'response', response: jsonResponse({ hello: 'world' }) };
1220
+ * },
1221
+ * });
1222
+ * ```
1223
+ */
1224
+ export function registerPlugin(plugin) {
1225
+ return plugin;
1226
+ }
1227
+ //# sourceMappingURL=index.js.map