timeback-studio 0.1.7 → 0.1.8

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/bin.js CHANGED
@@ -22937,6 +22937,7 @@ function createLogger(options = {}) {
22937
22937
  }
22938
22938
  // src/server/services/bootstrap.ts
22939
22939
  var log = createLogger({ scope: "studio:service:bootstrap" });
22940
+ var hasLoggedInitialLoad = false;
22940
22941
 
22941
22942
  class BootstrapService {
22942
22943
  client;
@@ -22967,8 +22968,11 @@ class BootstrapService {
22967
22968
  stats,
22968
22969
  errors: errors3.length
22969
22970
  });
22970
- const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
22971
- M2.success(message);
22971
+ if (!hasLoggedInitialLoad) {
22972
+ const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
22973
+ M2.success(message);
22974
+ hasLoggedInitialLoad = true;
22975
+ }
22972
22976
  return {
22973
22977
  user,
22974
22978
  courses,
@@ -23448,48 +23452,107 @@ class EventsService {
23448
23452
  return result;
23449
23453
  }
23450
23454
  }
23451
- // src/server/services/live.ts
23452
- var log4 = createLogger({ scope: "studio:service:live" });
23455
+ // src/server/services/live-poller.ts
23456
+ var log4 = createLogger({ scope: "studio:service:live-poller" });
23457
+ var pollers = new Map;
23458
+ function getOrCreatePoller(environment, statusService, eventsService) {
23459
+ let poller = pollers.get(environment);
23460
+ if (!poller) {
23461
+ poller = new LivePoller({ environment, statusService, eventsService });
23462
+ pollers.set(environment, poller);
23463
+ }
23464
+ return poller;
23465
+ }
23453
23466
 
23454
- class LiveService {
23467
+ class LivePoller {
23468
+ environment;
23455
23469
  statusService;
23456
23470
  eventsService;
23471
+ subscribers = new Set;
23472
+ interval = null;
23473
+ state;
23474
+ lastBroadcast;
23475
+ initialTickPromise = null;
23476
+ ticking = false;
23457
23477
  constructor(options) {
23478
+ this.environment = options.environment;
23458
23479
  this.statusService = options.statusService;
23459
23480
  this.eventsService = options.eventsService;
23460
- }
23461
- static createInitialState() {
23462
23481
  const now = Date.now();
23463
- return {
23482
+ this.state = {
23464
23483
  lastStatusHash: "",
23465
23484
  lastEventTime: new Date().toISOString(),
23466
23485
  lastStatusTick: now,
23467
23486
  lastEventsTick: now
23468
23487
  };
23469
23488
  }
23470
- async tick(state) {
23471
- const updates = [];
23472
- const now = Date.now();
23473
- const newState = { ...state };
23474
- const statusUpdate = await this.fetchStatusIfChanged(state.lastStatusHash);
23475
- if (statusUpdate) {
23476
- updates.push(statusUpdate.update);
23477
- newState.lastStatusHash = statusUpdate.hash;
23489
+ subscribe(callback) {
23490
+ this.subscribers.add(callback);
23491
+ if (this.subscribers.size === 1) {
23492
+ this.start();
23478
23493
  }
23479
- newState.lastStatusTick = now;
23480
- const shouldCheckEvents = this.hasIntervalElapsed(state.lastEventsTick, now, constants.events.polling.tickIntervalMs);
23481
- if (shouldCheckEvents) {
23482
- const eventsUpdate = await this.fetchEventsIfNew(state.lastEventTime);
23483
- if (eventsUpdate) {
23484
- updates.push(eventsUpdate.update);
23485
- newState.lastEventTime = eventsUpdate.newEventTime;
23494
+ return () => {
23495
+ this.subscribers.delete(callback);
23496
+ if (this.subscribers.size === 0) {
23497
+ this.stop();
23498
+ pollers.delete(this.environment);
23486
23499
  }
23487
- newState.lastEventsTick = now;
23500
+ };
23501
+ }
23502
+ async getLastBroadcastAfterInitialTick() {
23503
+ if (this.initialTickPromise) {
23504
+ await this.initialTickPromise;
23488
23505
  }
23489
- return { updates, newState };
23506
+ return this.lastBroadcast;
23490
23507
  }
23491
- hasIntervalElapsed(lastTick, now, intervalMs) {
23492
- return now - lastTick >= intervalMs;
23508
+ start() {
23509
+ log4.debug("Poller started", { environment: this.environment });
23510
+ this.initialTickPromise = this.tick();
23511
+ this.interval = setInterval(() => this.tick(), constants.sse.defaultTickIntervalMs);
23512
+ }
23513
+ stop() {
23514
+ if (this.interval) {
23515
+ clearInterval(this.interval);
23516
+ this.interval = null;
23517
+ }
23518
+ this.lastBroadcast = undefined;
23519
+ this.initialTickPromise = null;
23520
+ log4.debug("Poller stopped", { environment: this.environment });
23521
+ }
23522
+ async tick() {
23523
+ if (this.ticking)
23524
+ return;
23525
+ this.ticking = true;
23526
+ try {
23527
+ const updates = [];
23528
+ const now = Date.now();
23529
+ const statusUpdate = await this.fetchStatusIfChanged(this.state.lastStatusHash);
23530
+ if (statusUpdate) {
23531
+ updates.push(statusUpdate.update);
23532
+ this.state.lastStatusHash = statusUpdate.hash;
23533
+ }
23534
+ this.state.lastStatusTick = now;
23535
+ const shouldCheckEvents = now - this.state.lastEventsTick >= constants.events.polling.tickIntervalMs;
23536
+ if (shouldCheckEvents) {
23537
+ const eventsUpdate = await this.fetchEventsIfNew(this.state.lastEventTime);
23538
+ if (eventsUpdate) {
23539
+ updates.push(eventsUpdate.update);
23540
+ this.state.lastEventTime = eventsUpdate.newEventTime;
23541
+ }
23542
+ this.state.lastEventsTick = now;
23543
+ }
23544
+ if (updates.length > 0) {
23545
+ const data = JSON.stringify(updates);
23546
+ this.lastBroadcast = data;
23547
+ for (const callback of this.subscribers) {
23548
+ callback(data);
23549
+ }
23550
+ }
23551
+ } catch (error48) {
23552
+ log4.error("Tick failed", { environment: this.environment, error: error48 });
23553
+ } finally {
23554
+ this.ticking = false;
23555
+ }
23493
23556
  }
23494
23557
  async fetchStatusIfChanged(lastHash) {
23495
23558
  try {
@@ -23516,7 +23579,8 @@ class LiveService {
23516
23579
  }
23517
23580
  const message = `${underline(newEvents.length)} new ${pluralize(newEvents.length, "event")}`;
23518
23581
  M2.message(message, { symbol: magenta("◆") });
23519
- const mostRecentTime = newEvents.map((e2) => e2.eventTime).sort().pop();
23582
+ const mostRecentTime = newEvents.map((e2) => new Date(e2.eventTime).getTime()).sort((a, b3) => a - b3).pop();
23583
+ const newEventTime = mostRecentTime === undefined ? lastEventTime : new Date(mostRecentTime + 1).toISOString();
23520
23584
  return {
23521
23585
  update: {
23522
23586
  type: "events",
@@ -23525,7 +23589,7 @@ class LiveService {
23525
23589
  timestamp: new Date().toISOString()
23526
23590
  }
23527
23591
  },
23528
- newEventTime: mostRecentTime ?? lastEventTime
23592
+ newEventTime
23529
23593
  };
23530
23594
  } catch (error48) {
23531
23595
  log4.error("Failed to fetch events", { error: error48 });
@@ -23930,6 +23994,7 @@ function runSSE(c, options) {
23930
23994
  } catch {
23931
23995
  abort("write error");
23932
23996
  }
23997
+ options.onClose?.();
23933
23998
  unregisterConnection(key, () => abort("superseded"));
23934
23999
  log8.debug("SSE connection closed", { ...meta3, reason: closeReason });
23935
24000
  });
@@ -24032,20 +24097,41 @@ function handleEventsStream(c) {
24032
24097
  }
24033
24098
  // src/server/controllers/live.ts
24034
24099
  function handleLive(c) {
24100
+ const env2 = c.get("env");
24035
24101
  const { status: statusService, events: eventsService } = c.get("services");
24036
- const liveService = new LiveService({ statusService, eventsService });
24037
- let state = LiveService.createInitialState();
24102
+ const poller = getOrCreatePoller(env2, statusService, eventsService);
24103
+ let pending;
24104
+ let isFirstTick = true;
24105
+ const takePending = () => {
24106
+ const data = pending;
24107
+ pending = undefined;
24108
+ return data;
24109
+ };
24110
+ const unsubscribe = poller.subscribe((data) => {
24111
+ pending = data;
24112
+ });
24038
24113
  return runSSE(c, {
24039
24114
  event: "live",
24040
24115
  intervalMs: constants.sse.defaultTickIntervalMs,
24041
24116
  sendInitial: true,
24117
+ onClose: unsubscribe,
24042
24118
  tick: async () => {
24043
- const result = await liveService.tick(state);
24044
- state = result.newState;
24045
- if (result.updates.length === 0) {
24046
- return;
24119
+ if (isFirstTick) {
24120
+ isFirstTick = false;
24121
+ const immediate = takePending();
24122
+ if (immediate !== undefined) {
24123
+ return immediate;
24124
+ }
24125
+ const initial = await poller.getLastBroadcastAfterInitialTick();
24126
+ if (pending !== undefined && pending === initial) {
24127
+ pending = undefined;
24128
+ }
24129
+ if (initial !== undefined) {
24130
+ return initial;
24131
+ }
24132
+ return takePending();
24047
24133
  }
24048
- return JSON.stringify(result.updates);
24134
+ return takePending();
24049
24135
  }
24050
24136
  });
24051
24137
  }
package/dist/index.js CHANGED
@@ -20827,6 +20827,7 @@ function createLogger(options = {}) {
20827
20827
  }
20828
20828
  // src/server/services/bootstrap.ts
20829
20829
  var log = createLogger({ scope: "studio:service:bootstrap" });
20830
+ var hasLoggedInitialLoad = false;
20830
20831
 
20831
20832
  class BootstrapService {
20832
20833
  client;
@@ -20857,8 +20858,11 @@ class BootstrapService {
20857
20858
  stats,
20858
20859
  errors: errors3.length
20859
20860
  });
20860
- const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
20861
- M2.success(message);
20861
+ if (!hasLoggedInitialLoad) {
20862
+ const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
20863
+ M2.success(message);
20864
+ hasLoggedInitialLoad = true;
20865
+ }
20862
20866
  return {
20863
20867
  user,
20864
20868
  courses,
@@ -21338,48 +21342,107 @@ class EventsService {
21338
21342
  return result;
21339
21343
  }
21340
21344
  }
21341
- // src/server/services/live.ts
21342
- var log4 = createLogger({ scope: "studio:service:live" });
21345
+ // src/server/services/live-poller.ts
21346
+ var log4 = createLogger({ scope: "studio:service:live-poller" });
21347
+ var pollers = new Map;
21348
+ function getOrCreatePoller(environment, statusService, eventsService) {
21349
+ let poller = pollers.get(environment);
21350
+ if (!poller) {
21351
+ poller = new LivePoller({ environment, statusService, eventsService });
21352
+ pollers.set(environment, poller);
21353
+ }
21354
+ return poller;
21355
+ }
21343
21356
 
21344
- class LiveService {
21357
+ class LivePoller {
21358
+ environment;
21345
21359
  statusService;
21346
21360
  eventsService;
21361
+ subscribers = new Set;
21362
+ interval = null;
21363
+ state;
21364
+ lastBroadcast;
21365
+ initialTickPromise = null;
21366
+ ticking = false;
21347
21367
  constructor(options) {
21368
+ this.environment = options.environment;
21348
21369
  this.statusService = options.statusService;
21349
21370
  this.eventsService = options.eventsService;
21350
- }
21351
- static createInitialState() {
21352
21371
  const now = Date.now();
21353
- return {
21372
+ this.state = {
21354
21373
  lastStatusHash: "",
21355
21374
  lastEventTime: new Date().toISOString(),
21356
21375
  lastStatusTick: now,
21357
21376
  lastEventsTick: now
21358
21377
  };
21359
21378
  }
21360
- async tick(state) {
21361
- const updates = [];
21362
- const now = Date.now();
21363
- const newState = { ...state };
21364
- const statusUpdate = await this.fetchStatusIfChanged(state.lastStatusHash);
21365
- if (statusUpdate) {
21366
- updates.push(statusUpdate.update);
21367
- newState.lastStatusHash = statusUpdate.hash;
21379
+ subscribe(callback) {
21380
+ this.subscribers.add(callback);
21381
+ if (this.subscribers.size === 1) {
21382
+ this.start();
21368
21383
  }
21369
- newState.lastStatusTick = now;
21370
- const shouldCheckEvents = this.hasIntervalElapsed(state.lastEventsTick, now, constants.events.polling.tickIntervalMs);
21371
- if (shouldCheckEvents) {
21372
- const eventsUpdate = await this.fetchEventsIfNew(state.lastEventTime);
21373
- if (eventsUpdate) {
21374
- updates.push(eventsUpdate.update);
21375
- newState.lastEventTime = eventsUpdate.newEventTime;
21384
+ return () => {
21385
+ this.subscribers.delete(callback);
21386
+ if (this.subscribers.size === 0) {
21387
+ this.stop();
21388
+ pollers.delete(this.environment);
21376
21389
  }
21377
- newState.lastEventsTick = now;
21390
+ };
21391
+ }
21392
+ async getLastBroadcastAfterInitialTick() {
21393
+ if (this.initialTickPromise) {
21394
+ await this.initialTickPromise;
21378
21395
  }
21379
- return { updates, newState };
21396
+ return this.lastBroadcast;
21380
21397
  }
21381
- hasIntervalElapsed(lastTick, now, intervalMs) {
21382
- return now - lastTick >= intervalMs;
21398
+ start() {
21399
+ log4.debug("Poller started", { environment: this.environment });
21400
+ this.initialTickPromise = this.tick();
21401
+ this.interval = setInterval(() => this.tick(), constants.sse.defaultTickIntervalMs);
21402
+ }
21403
+ stop() {
21404
+ if (this.interval) {
21405
+ clearInterval(this.interval);
21406
+ this.interval = null;
21407
+ }
21408
+ this.lastBroadcast = undefined;
21409
+ this.initialTickPromise = null;
21410
+ log4.debug("Poller stopped", { environment: this.environment });
21411
+ }
21412
+ async tick() {
21413
+ if (this.ticking)
21414
+ return;
21415
+ this.ticking = true;
21416
+ try {
21417
+ const updates = [];
21418
+ const now = Date.now();
21419
+ const statusUpdate = await this.fetchStatusIfChanged(this.state.lastStatusHash);
21420
+ if (statusUpdate) {
21421
+ updates.push(statusUpdate.update);
21422
+ this.state.lastStatusHash = statusUpdate.hash;
21423
+ }
21424
+ this.state.lastStatusTick = now;
21425
+ const shouldCheckEvents = now - this.state.lastEventsTick >= constants.events.polling.tickIntervalMs;
21426
+ if (shouldCheckEvents) {
21427
+ const eventsUpdate = await this.fetchEventsIfNew(this.state.lastEventTime);
21428
+ if (eventsUpdate) {
21429
+ updates.push(eventsUpdate.update);
21430
+ this.state.lastEventTime = eventsUpdate.newEventTime;
21431
+ }
21432
+ this.state.lastEventsTick = now;
21433
+ }
21434
+ if (updates.length > 0) {
21435
+ const data = JSON.stringify(updates);
21436
+ this.lastBroadcast = data;
21437
+ for (const callback of this.subscribers) {
21438
+ callback(data);
21439
+ }
21440
+ }
21441
+ } catch (error48) {
21442
+ log4.error("Tick failed", { environment: this.environment, error: error48 });
21443
+ } finally {
21444
+ this.ticking = false;
21445
+ }
21383
21446
  }
21384
21447
  async fetchStatusIfChanged(lastHash) {
21385
21448
  try {
@@ -21406,7 +21469,8 @@ class LiveService {
21406
21469
  }
21407
21470
  const message = `${underline(newEvents.length)} new ${pluralize(newEvents.length, "event")}`;
21408
21471
  M2.message(message, { symbol: magenta("◆") });
21409
- const mostRecentTime = newEvents.map((e2) => e2.eventTime).sort().pop();
21472
+ const mostRecentTime = newEvents.map((e2) => new Date(e2.eventTime).getTime()).sort((a, b3) => a - b3).pop();
21473
+ const newEventTime = mostRecentTime === undefined ? lastEventTime : new Date(mostRecentTime + 1).toISOString();
21410
21474
  return {
21411
21475
  update: {
21412
21476
  type: "events",
@@ -21415,7 +21479,7 @@ class LiveService {
21415
21479
  timestamp: new Date().toISOString()
21416
21480
  }
21417
21481
  },
21418
- newEventTime: mostRecentTime ?? lastEventTime
21482
+ newEventTime
21419
21483
  };
21420
21484
  } catch (error48) {
21421
21485
  log4.error("Failed to fetch events", { error: error48 });
@@ -21820,6 +21884,7 @@ function runSSE(c, options) {
21820
21884
  } catch {
21821
21885
  abort("write error");
21822
21886
  }
21887
+ options.onClose?.();
21823
21888
  unregisterConnection(key, () => abort("superseded"));
21824
21889
  log8.debug("SSE connection closed", { ...meta3, reason: closeReason });
21825
21890
  });
@@ -21922,20 +21987,41 @@ function handleEventsStream(c) {
21922
21987
  }
21923
21988
  // src/server/controllers/live.ts
21924
21989
  function handleLive(c) {
21990
+ const env2 = c.get("env");
21925
21991
  const { status: statusService, events: eventsService } = c.get("services");
21926
- const liveService = new LiveService({ statusService, eventsService });
21927
- let state = LiveService.createInitialState();
21992
+ const poller = getOrCreatePoller(env2, statusService, eventsService);
21993
+ let pending;
21994
+ let isFirstTick = true;
21995
+ const takePending = () => {
21996
+ const data = pending;
21997
+ pending = undefined;
21998
+ return data;
21999
+ };
22000
+ const unsubscribe = poller.subscribe((data) => {
22001
+ pending = data;
22002
+ });
21928
22003
  return runSSE(c, {
21929
22004
  event: "live",
21930
22005
  intervalMs: constants.sse.defaultTickIntervalMs,
21931
22006
  sendInitial: true,
22007
+ onClose: unsubscribe,
21932
22008
  tick: async () => {
21933
- const result = await liveService.tick(state);
21934
- state = result.newState;
21935
- if (result.updates.length === 0) {
21936
- return;
22009
+ if (isFirstTick) {
22010
+ isFirstTick = false;
22011
+ const immediate = takePending();
22012
+ if (immediate !== undefined) {
22013
+ return immediate;
22014
+ }
22015
+ const initial = await poller.getLastBroadcastAfterInitialTick();
22016
+ if (pending !== undefined && pending === initial) {
22017
+ pending = undefined;
22018
+ }
22019
+ if (initial !== undefined) {
22020
+ return initial;
22021
+ }
22022
+ return takePending();
21937
22023
  }
21938
- return JSON.stringify(result.updates);
22024
+ return takePending();
21939
22025
  }
21940
22026
  });
21941
22027
  }
@@ -3,15 +3,17 @@
3
3
  *
4
4
  * GET /api/live - Multiplexed SSE stream combining status and events.
5
5
  *
6
- * See LiveService for detailed documentation on how tick intervals work.
6
+ * Uses a shared LivePoller per environment so all SSE connections receive
7
+ * the same data from a single polling loop. See LivePoller for details.
7
8
  */
8
9
  import type { Context } from 'hono';
9
10
  import type { EnvVariables } from '../lib';
10
11
  /**
11
12
  * GET /api/live - Multiplexed SSE stream for status and events.
12
13
  *
13
- * Combines status updates (on change) and event updates (every 30s) into
14
- * a single SSE connection. Emits an array of LiveUpdate objects.
14
+ * Subscribes to the shared LivePoller for the current environment.
15
+ * The poller broadcasts serialized updates which are forwarded to
16
+ * the client via the SSE tick function.
15
17
  *
16
18
  * @param c - Hono context with env variables
17
19
  * @returns SSE stream
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/server/controllers/live.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,YAqBjE"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/server/controllers/live.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAE1C;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,YAiDjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/server/lib/sse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AA6C5C;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ9C;AAMD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,YA4ExD"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/server/lib/sse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AA6C5C;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ9C;AAMD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,YA6ExD"}
@@ -60,5 +60,7 @@ export interface RunSSEOptions {
60
60
  sendInitial?: boolean;
61
61
  /** Return a string to emit, or undefined to skip this tick */
62
62
  tick: () => Promise<string | undefined>;
63
+ /** Called when the stream closes for any reason (superseded, client disconnect, error) */
64
+ onClose?: () => void;
63
65
  }
64
66
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAM/D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,gBAAgB,CAAA;IAC3B,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,aAAa,CAAA;IACrB,aAAa,EAAE,oBAAoB,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,+CAA+C;IAC/C,UAAU,EAAE,UAAU,CAAA;IACtB,oCAAoC;IACpC,MAAM,EAAE,cAAc,CAAA;IACtB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAMD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,WAAW,CAAA;IAChB,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,cAAc,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,KAAK,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,EAAE,CAAA;CACrB;AAMD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACvC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAM/D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,gBAAgB,CAAA;IAC3B,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,aAAa,CAAA;IACrB,aAAa,EAAE,oBAAoB,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,+CAA+C;IAC/C,UAAU,EAAE,UAAU,CAAA;IACtB,oCAAoC;IACpC,MAAM,EAAE,cAAc,CAAA;IACtB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAMD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,WAAW,CAAA;IAChB,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,cAAc,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,KAAK,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,EAAE,CAAA;CACrB;AAMD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACvC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/server/services/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGpD,OAAO,KAAK,EACX,eAAe,EAIf,mBAAmB,EACnB,MAAM,SAAS,CAAA;AAyBhB;;;;GAIG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAEvC,YAAY,MAAM,EAAE,cAAc,EAEjC;IAED;;;;;OAKG;IACG,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8CzE;IAMD;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;YAkBN,oBAAoB;YAoEpB,0BAA0B;YA8F1B,cAAc;YAgGd,YAAY;YAgCZ,gBAAgB;YA2BhB,gBAAgB;CAmH9B"}
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/server/services/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGpD,OAAO,KAAK,EACX,eAAe,EAIf,mBAAmB,EACnB,MAAM,SAAS,CAAA;AAiChB;;;;GAIG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAEvC,YAAY,MAAM,EAAE,cAAc,EAEjC;IAED;;;;;OAKG;IACG,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiDzE;IAMD;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;YAkBN,oBAAoB;YAoEpB,0BAA0B;YA8F1B,cAAc;YAgGd,YAAY;YAgCZ,gBAAgB;YA2BhB,gBAAgB;CAmH9B"}
@@ -6,8 +6,8 @@
6
6
  export { BootstrapService } from './bootstrap';
7
7
  export { EnrollmentService } from './enrollment';
8
8
  export { EventsService } from './events';
9
- export { LiveService } from './live';
9
+ export { getOrCreatePoller } from './live';
10
10
  export { StatusService } from './status';
11
11
  export { StudentSearchService } from './student-search';
12
- export type { BootstrapResult, BootstrapStats, EnrichedComponentResource, EnrichedEnrollment, GetBootstrapOptions, EnrollmentActionOptions, EnrollmentResult, EnrollStudentOptions, GetNewEventsOptions, GetRecentEventsOptions, TickResult, TickState, SearchStudentsOptions, StudentSearchResponse, StudentSearchResult, } from './types';
12
+ export type { BootstrapResult, BootstrapStats, EnrichedComponentResource, EnrichedEnrollment, GetBootstrapOptions, EnrollmentActionOptions, EnrollmentResult, EnrollStudentOptions, GetNewEventsOptions, GetRecentEventsOptions, TickState, SearchStudentsOptions, StudentSearchResponse, StudentSearchResult, } from './types';
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAMvD,YAAY,EAEX,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EAEnB,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EAEpB,mBAAmB,EACnB,sBAAsB,EAEtB,UAAU,EACV,SAAS,EAET,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACnB,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/services/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAMvD,YAAY,EAEX,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EAEnB,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EAEpB,mBAAmB,EACnB,sBAAsB,EAEtB,SAAS,EAET,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACnB,MAAM,SAAS,CAAA"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Live Poller
3
+ *
4
+ * Shared polling loop for the multiplexed /api/live SSE endpoint.
5
+ *
6
+ * ## Why This Exists
7
+ *
8
+ * The Studio server is a single-user process — every browser tab connects
9
+ * to the same backend and sees the same data. Rather than each SSE connection
10
+ * running its own polling loop (duplicating API calls), a single LivePoller
11
+ * per environment polls once and broadcasts to all subscribers.
12
+ *
13
+ * ## How It Works
14
+ *
15
+ * ```
16
+ * LivePoller (one per environment)
17
+ * => ticks every 1s
18
+ * => ALWAYS fetch status, emit only if hash changed
19
+ * => Every 30s, fetch events, emit if any new
20
+ * => broadcast serialized updates to all subscribers
21
+ * ```
22
+ *
23
+ * ## Lifecycle
24
+ *
25
+ * Ref-counted: the poller starts when the first SSE connection subscribes,
26
+ * and stops when the last one disconnects. No polling when nobody is listening.
27
+ *
28
+ * ## Data Sources
29
+ *
30
+ * | Source | Check Frequency | Emit Condition |
31
+ * |---------|-----------------|----------------------|
32
+ * | Status | Every tick (1s) | Payload hash changed |
33
+ * | Events | Every 30s | New events exist |
34
+ */
35
+ import type { EventsService } from './events';
36
+ import type { StatusService } from './status';
37
+ /**
38
+ * Get an existing poller for the environment, or create one.
39
+ *
40
+ * The poller is shared across all SSE connections for the same environment.
41
+ * It is removed from the registry when its last subscriber disconnects.
42
+ *
43
+ * @param environment - Environment key (e.g. 'staging', 'production')
44
+ * @param statusService - Status service instance
45
+ * @param eventsService - Events service instance
46
+ * @returns The shared LivePoller for this environment
47
+ */
48
+ export declare function getOrCreatePoller(environment: string, statusService: StatusService, eventsService: EventsService): LivePoller;
49
+ /**
50
+ * Options for creating a LivePoller.
51
+ */
52
+ interface LivePollerOptions {
53
+ environment: string;
54
+ statusService: StatusService;
55
+ eventsService: EventsService;
56
+ }
57
+ /** Callback invoked when the poller has new data to broadcast. */
58
+ type Subscriber = (data: string) => void;
59
+ /**
60
+ * Shared polling loop for a single environment.
61
+ *
62
+ * Owns the tick state (status hash, event cursor) and broadcasts
63
+ * serialized updates to all subscribed SSE connections.
64
+ */
65
+ export declare class LivePoller {
66
+ private readonly environment;
67
+ private readonly statusService;
68
+ private readonly eventsService;
69
+ private readonly subscribers;
70
+ private interval;
71
+ private state;
72
+ private lastBroadcast;
73
+ private initialTickPromise;
74
+ private ticking;
75
+ constructor(options: LivePollerOptions);
76
+ /**
77
+ * Subscribe to updates from this poller.
78
+ *
79
+ * Starts the polling loop on the first subscriber.
80
+ * Returns an unsubscribe function that stops polling when the last subscriber leaves.
81
+ *
82
+ * @param callback - Called with serialized LiveUpdate[] JSON when new data is available
83
+ * @returns Unsubscribe function
84
+ */
85
+ subscribe(callback: Subscriber): () => void;
86
+ /**
87
+ * Wait for the initial tick (triggered when polling starts) and then
88
+ * return the most recent broadcast payload.
89
+ *
90
+ * This is used by newly connected SSE clients so the initial send can
91
+ * include up-to-date data even if the first poll is still in progress.
92
+ *
93
+ * @returns Last broadcast JSON string, or undefined
94
+ */
95
+ getLastBroadcastAfterInitialTick(): Promise<string | undefined>;
96
+ private start;
97
+ private stop;
98
+ private tick;
99
+ private fetchStatusIfChanged;
100
+ private fetchEventsIfNew;
101
+ }
102
+ export {};
103
+ //# sourceMappingURL=live-poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-poller.d.ts","sourceRoot":"","sources":["../../../src/server/services/live-poller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAYH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAW7C;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAChC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,aAAa,GAC1B,UAAU,CASZ;AAMD;;GAEG;AACH,UAAU,iBAAiB;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;CAC5B;AAED,kEAAkE;AAClE,KAAK,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;AAExC;;;;;GAKG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAE7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;IACpD,OAAO,CAAC,QAAQ,CAA8C;IAC9D,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,OAAO,CAAQ;IAEvB,YAAY,OAAO,EAAE,iBAAiB,EAYrC;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,IAAI,CAe1C;IAED;;;;;;;;OAQG;IACG,gCAAgC,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAKpE;IAMD,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,IAAI;YAeE,IAAI;YA8DJ,oBAAoB;YA4BpB,gBAAgB;CA2C9B"}
@@ -1,83 +1,8 @@
1
1
  /**
2
- * Live Service
2
+ * Live Service (re-export)
3
3
  *
4
- * Orchestrates multiple data sources for the multiplexed /api/live SSE endpoint.
5
- *
6
- * ## How It Works
7
- *
8
- * The SSE connection ticks every 1 second (base interval). On each tick,
9
- * this service decides what to check based on each data source's rules:
10
- *
11
- * ```
12
- * runSSE setInterval(1s)
13
- * => every tick, calls liveService.tick(state)
14
- *
15
- * liveService.tick():
16
- * => ALWAYS fetch status, but only emit if hash changed
17
- * => Check if 30s elapsed since lastEventsTick:
18
- * - YES: fetch events, emit if any new
19
- * - NO: skip events entirely
20
- *
21
- * => return { updates: [...], newState }
22
- * ```
23
- *
24
- * ## Data Sources
25
- *
26
- * | Source | Check Frequency | Emit Condition |
27
- * |---------|-----------------|---------------------|
28
- * | Status | Every tick (1s) | Payload hash changed |
29
- * | Events | Every 30s | New events exist |
30
- *
31
- * This means 29 out of 30 ticks only check status. The 30th tick checks both.
32
- */
33
- import type { EventsService } from './events';
34
- import type { StatusService } from './status';
35
- import type { TickResult, TickState } from './types';
36
- /**
37
- * Options for creating a LiveService.
38
- */
39
- interface LiveServiceOptions {
40
- statusService: StatusService;
41
- eventsService: EventsService;
42
- }
43
- /**
44
- * Service that orchestrates status and event updates for the multiplexed SSE endpoint.
45
- *
46
- * Each SSE connection gets its own instance. The service manages independent tick
47
- * intervals: status is checked every tick (1s) but only emitted on hash change,
48
- * while events are fetched every 30s and emitted when new events exist.
4
+ * The shared LivePoller replaces the per-connection LiveService.
5
+ * This file re-exports for barrel compatibility.
49
6
  */
50
- export declare class LiveService {
51
- private readonly statusService;
52
- private readonly eventsService;
53
- constructor(options: LiveServiceOptions);
54
- /**
55
- * Create initial tick state for a new SSE connection.
56
- *
57
- * @returns Fresh tick state with empty hash and current timestamps
58
- */
59
- static createInitialState(): TickState;
60
- /**
61
- * Perform a tick operation.
62
- *
63
- * Called every base tick (1s) by the SSE loop. Checks each data source
64
- * according to its rules and returns any updates to emit.
65
- *
66
- * @param state - Current tick state from previous tick
67
- * @returns Updates to emit and new state for next tick
68
- */
69
- tick(state: TickState): Promise<TickResult>;
70
- /**
71
- * Check if enough time has passed since the last check.
72
- *
73
- * @param lastTick - Timestamp (ms) of last check
74
- * @param now - Current timestamp (ms)
75
- * @param intervalMs - Required interval between checks
76
- * @returns True if interval has elapsed
77
- */
78
- private hasIntervalElapsed;
79
- private fetchStatusIfChanged;
80
- private fetchEventsIfNew;
81
- }
82
- export {};
7
+ export { getOrCreatePoller } from './live-poller';
83
8
  //# sourceMappingURL=live.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/server/services/live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAIpD;;GAEG;AACH,UAAU,kBAAkB;IAC3B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAE7C,YAAY,OAAO,EAAE,kBAAkB,EAGtC;IAED;;;;OAIG;IACH,MAAM,CAAC,kBAAkB,IAAI,SAAS,CAQrC;IAED;;;;;;;;OAQG;IACG,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAoChD;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;YAUZ,oBAAoB;YA4BpB,gBAAgB;CAiC9B"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/server/services/live.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA"}
@@ -7,5 +7,5 @@ export type { BootstrapResult, BootstrapStats, EnrichedComponentResource, Enrich
7
7
  export type { EnrollmentActionOptions, EnrollmentResult, EnrollStudentOptions } from './enrollment';
8
8
  export type { GetNewEventsOptions, GetRecentEventsOptions } from './events';
9
9
  export type { SearchStudentsOptions, StudentSearchResponse, StudentSearchResult, } from './student-search';
10
- export type { TickResult, TickState } from './live';
10
+ export type { TickState } from './live';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/services/types/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACX,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAEnG,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAE3E,YAAY,EACX,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/services/types/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACX,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAEnG,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAE3E,YAAY,EACX,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Live Service Types
2
+ * Live Poller Types
3
3
  *
4
4
  * Types for the multiplexed SSE endpoint that combines status and events.
5
5
  */
6
- import type { LiveUpdate } from '../../../types';
7
6
  /**
8
- * Per-connection state for tracking when to check each data source.
7
+ * State for tracking when to check each data source.
9
8
  *
10
- * Each SSE connection maintains its own TickState. The LiveService uses
11
- * this state to determine:
9
+ * Owned by the shared LivePoller (one per environment). Used to determine:
12
10
  * - Status: Has the payload hash changed since last emit?
13
11
  * - Events: Has 30s elapsed since last check?
14
12
  */
@@ -22,13 +20,4 @@ export interface TickState {
22
20
  /** Timestamp (ms) of last events check. Used to enforce 30s interval. */
23
21
  lastEventsTick: number;
24
22
  }
25
- /**
26
- * Result of a tick operation.
27
- */
28
- export interface TickResult {
29
- /** Updates to emit to the client (may be empty if nothing changed) */
30
- updates: LiveUpdate[];
31
- /** Updated state to pass to the next tick */
32
- newState: TickState;
33
- }
34
23
  //# sourceMappingURL=live.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../../src/server/services/types/live.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IAKzB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAA;IAEtB,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAA;IAMtB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAA;IAErB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,OAAO,EAAE,UAAU,EAAE,CAAA;IAErB,6CAA6C;IAC7C,QAAQ,EAAE,SAAS,CAAA;CACnB"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../../src/server/services/types/live.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IAKzB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAA;IAEtB,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAA;IAMtB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAA;IAErB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAA;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "timeback-studio",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@clack/prompts": "^0.11.0",
29
29
  "@hono/node-server": "^1.19.7",
30
- "@timeback/core": "0.1.4",
30
+ "@timeback/core": "0.1.5",
31
31
  "c12": "^3.3.3",
32
32
  "colorette": "^2.0.20",
33
33
  "commander": "^14.0.2",