toilscript 0.1.49 → 0.1.51

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/web.js CHANGED
@@ -1,8 +1,8 @@
1
- var ASSEMBLYSCRIPT_VERSION = "0.1.49";
1
+ var ASSEMBLYSCRIPT_VERSION = "0.1.51";
2
2
  var ASSEMBLYSCRIPT_IMPORTMAP = {
3
3
  "imports": {
4
- "toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.49/dist/toilscript.js",
5
- "toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.49/dist/cli.js",
4
+ "toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.51/dist/toilscript.js",
5
+ "toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.51/dist/cli.js",
6
6
  "binaryen": "https://cdn.jsdelivr.net/npm/binaryen@130.0.0-nightly.20260609/index.js",
7
7
  "long": "https://cdn.jsdelivr.net/npm/long@5.3.2/index.js"
8
8
  }
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "toilscript",
9
9
  "wasm"
10
10
  ],
11
- "version": "0.1.49",
11
+ "version": "0.1.51",
12
12
  "author": "Daniel Wirtz <dcode+assemblyscript@dcode.io>",
13
13
  "license": "Apache-2.0",
14
14
  "homepage": "https://github.com/dacely-cloud/toilscript",
@@ -283,6 +283,12 @@ export namespace analyticsHost {
283
283
  @external("env", "analytics_read")
284
284
  export declare function read(domainPtr: usize, domainLen: i32): i32;
285
285
 
286
+ // analytics_series(domain, metricId, range): frame length (v2 series frame) stashed for takeResult,
287
+ // or a negative status (-2 absent / -3 forbidden / bad metric or range).
288
+ // @ts-ignore: decorator
289
+ @external("env", "analytics_series")
290
+ export declare function series(domainPtr: usize, domainLen: i32, metricId: i32, range: i32): i32;
291
+
286
292
  // analytics_list_sites(cursor, limit): frame length (count u32 | (u32 nameLen, name)* |
287
293
  // has_more u8) stashed for takeResult; -3 forbidden when the caller is not dacely.com.
288
294
  // @ts-ignore: decorator
@@ -438,16 +438,145 @@ export class Counter<K> {
438
438
  }
439
439
  }
440
440
 
441
- /// One tenant's analytics snapshot (the metering counters + plan limits), read via the
442
- /// `Analytics` API. `lifetime` holds the per-domain lifetime totals by metric name
443
- /// (`requests`, `bytes_served`, `status_2xx`.., `db_ops`, `stream_*`, ...); the request
444
- /// windows pair the current minute/day usage with the plan cap (cap 0 = unlimited).
441
+ /// Every per-domain metric, by stable numeric id. This is the WIRE CONTRACT shared with the edge
442
+ /// (`src/analytics/metric_id.rs`) and the dev server; it orders the snapshot frame and addresses a
443
+ /// time-series. Ids `0..=40` are cumulative COUNTERS (a rate is `value / seconds`); `41..=44` are the
444
+ /// avg/peak series of the two GAUGES. Append-only: never renumber.
445
+ export enum MetricId {
446
+ Requests = 0,
447
+ BytesOutL1 = 1,
448
+ BytesInL1 = 2,
449
+ Status2xx = 3,
450
+ Status3xx = 4,
451
+ Status4xx = 5,
452
+ Status5xx = 6,
453
+ StaticHits = 7,
454
+ WasmDispatches = 8,
455
+ ExecutorFullRejects = 9,
456
+ UnknownHostRejects = 10,
457
+ RateLimitedRejects = 11,
458
+ GasUsed = 12,
459
+ DbOps = 13,
460
+ DbReads = 14,
461
+ DbWrites = 15,
462
+ DbErrors = 16,
463
+ DbLatencyNsSum = 17,
464
+ StreamAccepts = 18,
465
+ StreamRejectWrongNode = 19,
466
+ StreamRejectCapacity = 20,
467
+ StreamRejectArtifact = 21,
468
+ StreamRejectGuest = 22,
469
+ StreamTraps = 23,
470
+ StreamIdleTimeouts = 24,
471
+ StreamBytesIn = 25,
472
+ StreamBytesOut = 26,
473
+ StreamBackpressureEvents = 27,
474
+ StreamCloses = 28,
475
+ StreamDisconnects = 29,
476
+ DaemonStarts = 30,
477
+ DaemonStartFailures = 31,
478
+ DaemonTicksFired = 32,
479
+ DaemonTicksSkippedNotLeader = 33,
480
+ DaemonTicksFailed = 34,
481
+ DaemonLeaderAcquires = 35,
482
+ DaemonLeaderFenced = 36,
483
+ DaemonHttpCallAttempts = 37,
484
+ DaemonHttpCallFailures = 38,
485
+ MemGrownBytes = 39,
486
+ Emails = 40,
487
+ ConnectedStreamsAvg = 41,
488
+ ConnectedStreamsPeak = 42,
489
+ CommittedMemoryAvg = 43,
490
+ CommittedMemoryPeak = 44,
491
+ }
492
+
493
+ /// The number of cumulative counter metrics (`MetricId 0..=40`) = the length of the snapshot's lifetime
494
+ /// section.
495
+ export const METRIC_COUNTERS: i32 = 41;
496
+
497
+ /// A dashboard time range for `Analytics.series`. Short ranges (1h/6h) read the per-MINUTE ring; the rest
498
+ /// read the per-HOUR ring (30-day retention).
499
+ export enum AnalyticsRange {
500
+ H1 = 0,
501
+ H6 = 1,
502
+ H12 = 2,
503
+ H24 = 3,
504
+ D3 = 4,
505
+ D7 = 5,
506
+ D14 = 6,
507
+ D30 = 7,
508
+ }
509
+
510
+ /// One tenant's analytics snapshot: the lifetime totals (indexed by `MetricId`, NO string keys), the two
511
+ /// live gauge levels, and the request windows paired with the plan caps (cap 0 = unlimited). Read every
512
+ /// value either by the typed getter (`stats.requests`) or by id (`stats.metric(MetricId.Requests)`).
445
513
  export class TenantStats {
446
- lifetime: Map<string, i64> = new Map<string, i64>();
514
+ /// Lifetime totals indexed by `MetricId` (length `METRIC_COUNTERS`). Prefer the named getters below.
515
+ life: StaticArray<i64> = new StaticArray<i64>(METRIC_COUNTERS);
516
+ /// Live gauges (current level, not a total).
517
+ connectedStreams: i64 = 0;
518
+ committedMemory: i64 = 0;
519
+ /// Request windows: current bucket usage + plan cap (0 = unlimited).
447
520
  reqMinuteUsed: i64 = 0;
448
521
  reqMinuteCap: u64 = 0;
449
522
  reqDayUsed: i64 = 0;
450
523
  reqDayCap: u64 = 0;
524
+ /// Edge wall-clock (ms) when the snapshot was read.
525
+ nowMs: u64 = 0;
526
+
527
+ /// Any counter metric by id (0 for an out-of-range id).
528
+ metric(id: MetricId): i64 {
529
+ return <i32>id < METRIC_COUNTERS ? this.life[<i32>id] : 0;
530
+ }
531
+
532
+ // Typed getters for every counter (the catalog: no magic strings, self-documenting).
533
+ get requests(): i64 { return this.life[MetricId.Requests]; }
534
+ get bytesOutL1(): i64 { return this.life[MetricId.BytesOutL1]; }
535
+ get bytesInL1(): i64 { return this.life[MetricId.BytesInL1]; }
536
+ get status2xx(): i64 { return this.life[MetricId.Status2xx]; }
537
+ get status3xx(): i64 { return this.life[MetricId.Status3xx]; }
538
+ get status4xx(): i64 { return this.life[MetricId.Status4xx]; }
539
+ get status5xx(): i64 { return this.life[MetricId.Status5xx]; }
540
+ get staticHits(): i64 { return this.life[MetricId.StaticHits]; }
541
+ get wasmDispatches(): i64 { return this.life[MetricId.WasmDispatches]; }
542
+ get executorFullRejects(): i64 { return this.life[MetricId.ExecutorFullRejects]; }
543
+ get unknownHostRejects(): i64 { return this.life[MetricId.UnknownHostRejects]; }
544
+ get rateLimitedRejects(): i64 { return this.life[MetricId.RateLimitedRejects]; }
545
+ get gasUsed(): i64 { return this.life[MetricId.GasUsed]; }
546
+ get dbOps(): i64 { return this.life[MetricId.DbOps]; }
547
+ get dbReads(): i64 { return this.life[MetricId.DbReads]; }
548
+ get dbWrites(): i64 { return this.life[MetricId.DbWrites]; }
549
+ get dbErrors(): i64 { return this.life[MetricId.DbErrors]; }
550
+ get dbLatencyNsSum(): i64 { return this.life[MetricId.DbLatencyNsSum]; }
551
+ get streamAccepts(): i64 { return this.life[MetricId.StreamAccepts]; }
552
+ get streamBytesIn(): i64 { return this.life[MetricId.StreamBytesIn]; }
553
+ get streamBytesOut(): i64 { return this.life[MetricId.StreamBytesOut]; }
554
+ get streamCloses(): i64 { return this.life[MetricId.StreamCloses]; }
555
+ get streamDisconnects(): i64 { return this.life[MetricId.StreamDisconnects]; }
556
+ get daemonTicks(): i64 { return this.life[MetricId.DaemonTicksFired]; }
557
+ get memGrownBytes(): i64 { return this.life[MetricId.MemGrownBytes]; }
558
+ get emails(): i64 { return this.life[MetricId.Emails]; }
559
+
560
+ /// Mean host-observed DB op latency (ns), or 0 with no ops.
561
+ get meanDbLatencyNs(): i64 {
562
+ const ops = this.dbOps;
563
+ return ops > 0 ? this.dbLatencyNsSum / ops : 0;
564
+ }
565
+ }
566
+
567
+ /// One metric's time series for a range: `points` oldest→newest, `bucketSecs` the per-bucket width, and
568
+ /// `headMs` the newest bucket's end. Rates are derived, never stored.
569
+ export class Series {
570
+ metric: MetricId = MetricId.Requests;
571
+ bucketSecs: u32 = 0;
572
+ headMs: u64 = 0;
573
+ points: i64[] = [];
574
+
575
+ /// Per-second rate of bucket `i` (its total divided by the bucket width). 0 for an out-of-range index.
576
+ ratePerSec(i: i32): f64 {
577
+ if (i < 0 || i >= this.points.length || this.bucketSecs == 0) return 0;
578
+ return <f64>this.points[i] / <f64>this.bucketSecs;
579
+ }
451
580
  }
452
581
 
453
582
  /// A page of site names from `Analytics.listSites` (dacely.com only). When `hasMore` is
@@ -465,12 +594,16 @@ export class Analytics {
465
594
  private static decode(buf: Uint8Array): TenantStats {
466
595
  const r = new DataReader(buf);
467
596
  const stats = new TenantStats();
468
- r.readU16(); // frame version (currently 1)
469
- const count = r.readU32();
470
- for (let i: u32 = 0; i < count && r.ok; i++) {
471
- const name = r.readString();
472
- stats.lifetime.set(name, r.readI64());
597
+ const version = r.readU16();
598
+ if (version < 2) return stats; // pre-v2 edge: refuse rather than mis-decode
599
+ stats.nowMs = r.readU64();
600
+ const count = <i32>r.readU32();
601
+ for (let i: i32 = 0; i < count && r.ok; i++) {
602
+ const v = r.readI64();
603
+ if (i < METRIC_COUNTERS) stats.life[i] = v; // ignore any extra (forward-compat)
473
604
  }
605
+ stats.connectedStreams = r.readI64();
606
+ stats.committedMemory = r.readI64();
474
607
  stats.reqMinuteUsed = r.readI64();
475
608
  stats.reqMinuteCap = r.readU64();
476
609
  stats.reqDayUsed = r.readI64();
@@ -478,6 +611,21 @@ export class Analytics {
478
611
  return stats;
479
612
  }
480
613
 
614
+ private static decodeSeries(buf: Uint8Array): Series {
615
+ const r = new DataReader(buf);
616
+ const out = new Series();
617
+ const version = r.readU16();
618
+ if (version < 2) return out;
619
+ out.metric = <MetricId>r.readU16();
620
+ out.bucketSecs = r.readU32();
621
+ out.headMs = r.readU64();
622
+ const count = <i32>r.readU32();
623
+ const pts = new Array<i64>(count);
624
+ for (let i: i32 = 0; i < count && r.ok; i++) pts[i] = r.readI64();
625
+ out.points = pts;
626
+ return out;
627
+ }
628
+
481
629
  /// This site's own analytics. Returns empty stats if unavailable.
482
630
  static self(): TenantStats {
483
631
  const status = analyticsHost.read(0, 0);
@@ -494,6 +642,21 @@ export class Analytics {
494
642
  return Analytics.decode(__toildbTake(status));
495
643
  }
496
644
 
645
+ /// This site's time-series for `metric` over `range` (for graphs). Empty series if unavailable.
646
+ static series(metric: MetricId, range: AnalyticsRange): Series {
647
+ const status = analyticsHost.series(0, 0, <i32>metric, <i32>range);
648
+ if (status < 0) return new Series();
649
+ return Analytics.decodeSeries(__toildbTake(status));
650
+ }
651
+
652
+ /// Another site's time-series (dacely.com only, else `null`).
653
+ static siteSeries(domain: string, metric: MetricId, range: AnalyticsRange): Series | null {
654
+ const db = Uint8Array.wrap(String.UTF8.encode(domain));
655
+ const status = analyticsHost.series(db.dataStart, db.byteLength, <i32>metric, <i32>range);
656
+ if (status < 0) return null;
657
+ return Analytics.decodeSeries(__toildbTake(status));
658
+ }
659
+
497
660
  /// Enumerate sites, paginated. ONLY `dacely.com` gets results; any other caller gets an
498
661
  /// empty list. `cursor` is the previous page's last name (`""` = from the start); reads up
499
662
  /// to `limit` names. When `hasMore` is true, pass the last `sites` entry as the next cursor.
@@ -282,19 +282,79 @@ declare class Events<K, V> {
282
282
  latest(key: K, limit: i32): V[];
283
283
  }
284
284
 
285
- /** One tenant's analytics snapshot: per-metric lifetime totals + the request rate
286
- * windows (current usage paired with the plan cap; cap 0 = unlimited). */
285
+ /** Every per-domain metric, by stable numeric id (the wire contract). `0..=40` are cumulative
286
+ * COUNTERS (rate = value/seconds); `41..=44` are the avg/peak series of the two GAUGES. */
287
+ declare const enum MetricId {
288
+ Requests = 0, BytesOutL1 = 1, BytesInL1 = 2,
289
+ Status2xx = 3, Status3xx = 4, Status4xx = 5, Status5xx = 6,
290
+ StaticHits = 7, WasmDispatches = 8,
291
+ ExecutorFullRejects = 9, UnknownHostRejects = 10, RateLimitedRejects = 11, GasUsed = 12,
292
+ DbOps = 13, DbReads = 14, DbWrites = 15, DbErrors = 16, DbLatencyNsSum = 17,
293
+ StreamAccepts = 18, StreamRejectWrongNode = 19, StreamRejectCapacity = 20, StreamRejectArtifact = 21,
294
+ StreamRejectGuest = 22, StreamTraps = 23, StreamIdleTimeouts = 24, StreamBytesIn = 25,
295
+ StreamBytesOut = 26, StreamBackpressureEvents = 27, StreamCloses = 28, StreamDisconnects = 29,
296
+ DaemonStarts = 30, DaemonStartFailures = 31, DaemonTicksFired = 32, DaemonTicksSkippedNotLeader = 33,
297
+ DaemonTicksFailed = 34, DaemonLeaderAcquires = 35, DaemonLeaderFenced = 36,
298
+ DaemonHttpCallAttempts = 37, DaemonHttpCallFailures = 38, MemGrownBytes = 39, Emails = 40,
299
+ ConnectedStreamsAvg = 41, ConnectedStreamsPeak = 42, CommittedMemoryAvg = 43, CommittedMemoryPeak = 44,
300
+ }
301
+
302
+ /** A dashboard time range for `Analytics.series`. 1h/6h are per-minute; the rest per-hour (30-day). */
303
+ declare const enum AnalyticsRange {
304
+ H1 = 0, H6 = 1, H12 = 2, H24 = 3, D3 = 4, D7 = 5, D14 = 6, D30 = 7,
305
+ }
306
+
307
+ /** One tenant's analytics snapshot: lifetime totals (indexed by `MetricId`, no string keys), the two
308
+ * live gauge levels, and the request windows (current usage + plan cap; cap 0 = unlimited). Read a
309
+ * value by typed getter (`stats.requests`) or by id (`stats.metric(MetricId.Requests)`). */
287
310
  declare class TenantStats {
288
- lifetime: Map<string, i64>;
311
+ metric(id: MetricId): i64;
312
+ readonly requests: i64;
313
+ readonly bytesOutL1: i64;
314
+ readonly bytesInL1: i64;
315
+ readonly status2xx: i64;
316
+ readonly status3xx: i64;
317
+ readonly status4xx: i64;
318
+ readonly status5xx: i64;
319
+ readonly staticHits: i64;
320
+ readonly wasmDispatches: i64;
321
+ readonly executorFullRejects: i64;
322
+ readonly unknownHostRejects: i64;
323
+ readonly rateLimitedRejects: i64;
324
+ readonly gasUsed: i64;
325
+ readonly dbOps: i64;
326
+ readonly dbReads: i64;
327
+ readonly dbWrites: i64;
328
+ readonly dbErrors: i64;
329
+ readonly dbLatencyNsSum: i64;
330
+ readonly meanDbLatencyNs: i64;
331
+ readonly streamAccepts: i64;
332
+ readonly streamBytesIn: i64;
333
+ readonly streamBytesOut: i64;
334
+ readonly streamCloses: i64;
335
+ readonly streamDisconnects: i64;
336
+ readonly daemonTicks: i64;
337
+ readonly memGrownBytes: i64;
338
+ readonly emails: i64;
339
+ connectedStreams: i64;
340
+ committedMemory: i64;
289
341
  reqMinuteUsed: i64;
290
342
  reqMinuteCap: u64;
291
343
  reqDayUsed: i64;
292
344
  reqDayCap: u64;
345
+ nowMs: u64;
346
+ }
347
+
348
+ /** One metric's time series for a range: `points` oldest→newest, `bucketSecs` bucket width,
349
+ * `headMs` newest bucket end. `ratePerSec(i)` derives the per-second rate of point `i`. */
350
+ declare class Series {
351
+ metric: MetricId;
352
+ bucketSecs: u32;
353
+ headMs: u64;
354
+ points: i64[];
355
+ ratePerSec(i: i32): f64;
293
356
  }
294
357
 
295
- /** Per-domain analytics. `Analytics.self()` reads this site's own stats; the privileged
296
- * `dacely.com` domain may read any site via `Analytics.site(domain)` (any other caller,
297
- * or an unknown domain, gets `null`). The calling domain is decided host-side. */
298
358
  /** A page of site names from `Analytics.listSites` (dacely.com only). When `hasMore` is
299
359
  * true, pass the last `sites` entry back as the next call's cursor. */
300
360
  declare class SiteList {
@@ -302,9 +362,14 @@ declare class SiteList {
302
362
  hasMore: bool;
303
363
  }
304
364
 
365
+ /** Per-domain analytics. `Analytics.self()` reads this site's own stats; the privileged
366
+ * `dacely.com` domain may read any site via `Analytics.site(domain)` (any other caller,
367
+ * or an unknown domain, gets `null`). The calling domain is decided host-side. */
305
368
  declare class Analytics {
306
369
  static self(): TenantStats;
307
370
  static site(domain: string): TenantStats | null;
371
+ static series(metric: MetricId, range: AnalyticsRange): Series;
372
+ static siteSeries(domain: string, metric: MetricId, range: AnalyticsRange): Series | null;
308
373
  static listSites(cursor?: string, limit?: i32): SiteList;
309
374
  }
310
375