@orpc/experimental-publisher-durable-object 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -58,15 +58,15 @@ declare class ResumeStorage {
58
58
  * Inactivity means: no active connections AND no active events.
59
59
  */
60
60
  alarm(): Promise<void>;
61
- private ensureReady;
62
- private resetData;
61
+ private ensureSchemaAndCleanup;
62
+ private resetSchema;
63
63
  private scheduleAlarm;
64
64
  }
65
65
 
66
66
  interface PublisherDurableObjectOptions {
67
67
  resume?: ResumeStorageOptions;
68
68
  }
69
- declare class PublisherDurableObject<Env = Cloudflare.Env, Props = {}> extends DurableObject<Env, Props> {
69
+ declare class PublisherDurableObject<Env = Cloudflare.Env, Props = unknown> extends DurableObject<Env, Props> {
70
70
  private readonly resumeStorage;
71
71
  constructor(ctx: DurableObjectState<Props>, env: Env, options?: PublisherDurableObjectOptions);
72
72
  fetch(request: Request): Promise<Response>;
@@ -94,7 +94,7 @@ declare class DurablePublisher<T extends Record<string, object>> extends Publish
94
94
  private readonly prefix;
95
95
  private readonly serializer;
96
96
  private readonly getStubByName;
97
- constructor(namespace: DurableObjectNamespace, { prefix, getStubByName, ...options }?: DurablePublisherOptions);
97
+ constructor(namespace: DurableObjectNamespace<any>, { prefix, getStubByName, ...options }?: DurablePublisherOptions);
98
98
  publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
99
99
  protected subscribeListener<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
100
100
  }
package/dist/index.d.ts CHANGED
@@ -58,15 +58,15 @@ declare class ResumeStorage {
58
58
  * Inactivity means: no active connections AND no active events.
59
59
  */
60
60
  alarm(): Promise<void>;
61
- private ensureReady;
62
- private resetData;
61
+ private ensureSchemaAndCleanup;
62
+ private resetSchema;
63
63
  private scheduleAlarm;
64
64
  }
65
65
 
66
66
  interface PublisherDurableObjectOptions {
67
67
  resume?: ResumeStorageOptions;
68
68
  }
69
- declare class PublisherDurableObject<Env = Cloudflare.Env, Props = {}> extends DurableObject<Env, Props> {
69
+ declare class PublisherDurableObject<Env = Cloudflare.Env, Props = unknown> extends DurableObject<Env, Props> {
70
70
  private readonly resumeStorage;
71
71
  constructor(ctx: DurableObjectState<Props>, env: Env, options?: PublisherDurableObjectOptions);
72
72
  fetch(request: Request): Promise<Response>;
@@ -94,7 +94,7 @@ declare class DurablePublisher<T extends Record<string, object>> extends Publish
94
94
  private readonly prefix;
95
95
  private readonly serializer;
96
96
  private readonly getStubByName;
97
- constructor(namespace: DurableObjectNamespace, { prefix, getStubByName, ...options }?: DurablePublisherOptions);
97
+ constructor(namespace: DurableObjectNamespace<any>, { prefix, getStubByName, ...options }?: DurablePublisherOptions);
98
98
  publish<K extends keyof T & string>(event: K, payload: T[K]): Promise<void>;
99
99
  protected subscribeListener<K extends keyof T & string>(event: K, listener: (payload: T[K]) => void, options?: PublisherSubscribeListenerOptions): Promise<() => Promise<void>>;
100
100
  }
package/dist/index.mjs CHANGED
@@ -27,7 +27,7 @@ class ResumeStorage {
27
27
  if (!this.isEnabled) {
28
28
  return stringified;
29
29
  }
30
- await this.ensureReady();
30
+ await this.ensureSchemaAndCleanup();
31
31
  const message = JSON.parse(stringified);
32
32
  const insertEvent = () => {
33
33
  const result = this.ctx.storage.sql.exec(
@@ -43,8 +43,9 @@ class ResumeStorage {
43
43
  };
44
44
  try {
45
45
  return insertEvent();
46
- } catch {
47
- await this.resetData();
46
+ } catch (e) {
47
+ console.error("Failed to insert event, resetting resume storage schema.", e);
48
+ await this.resetSchema();
48
49
  return insertEvent();
49
50
  }
50
51
  }
@@ -55,7 +56,7 @@ class ResumeStorage {
55
56
  if (!this.isEnabled) {
56
57
  return [];
57
58
  }
58
- await this.ensureReady();
59
+ await this.ensureSchemaAndCleanup();
59
60
  const result = this.ctx.storage.sql.exec(`
60
61
  SELECT CAST(id AS TEXT) as id, payload
61
62
  FROM "${this.schemaPrefix}events"
@@ -79,62 +80,69 @@ class ResumeStorage {
79
80
  */
80
81
  async alarm() {
81
82
  this.isInitedAlarm = true;
82
- await this.ensureReady();
83
- const hasActiveWebSockets = this.ctx.getWebSockets().length > 0;
84
- if (hasActiveWebSockets) {
85
- await this.scheduleAlarm();
86
- return;
87
- }
88
- const activeEventsCount = this.ctx.storage.sql.exec(`
89
- SELECT COUNT(*) as count
90
- FROM "${this.schemaPrefix}events"
91
- `);
92
- const hasActiveEvents = activeEventsCount.one()?.count > 0;
93
- if (hasActiveEvents) {
94
- await this.scheduleAlarm();
95
- return;
96
- }
97
- await this.ctx.blockConcurrencyWhile(async () => {
83
+ await this.ensureSchemaAndCleanup();
84
+ const shouldReschedule = await this.ctx.blockConcurrencyWhile(async () => {
85
+ const hasActiveWebSockets = this.ctx.getWebSockets().length > 0;
86
+ if (hasActiveWebSockets) {
87
+ return true;
88
+ }
89
+ const activeEventsCount = this.ctx.storage.sql.exec(`
90
+ SELECT COUNT(*) as count
91
+ FROM "${this.schemaPrefix}events"
92
+ `);
93
+ const hasActiveEvents = activeEventsCount.one()?.count > 0;
94
+ if (hasActiveEvents) {
95
+ return true;
96
+ }
98
97
  this.isInitedSchema = false;
99
98
  this.isInitedAlarm = false;
100
99
  await this.ctx.storage.deleteAll();
100
+ return false;
101
101
  });
102
+ if (shouldReschedule) {
103
+ await this.scheduleAlarm();
104
+ }
102
105
  }
103
- async ensureReady() {
104
- if (this.isInitedSchema) {
105
- const now = Date.now();
106
- if (this.lastCleanupTime && this.lastCleanupTime + this.retentionSeconds * 1e3 > now) {
107
- return;
106
+ async ensureSchemaAndCleanup() {
107
+ if (!this.isInitedAlarm) {
108
+ const currentAlarm = await this.ctx.storage.getAlarm();
109
+ if (currentAlarm === null) {
110
+ await this.scheduleAlarm();
108
111
  }
109
- this.lastCleanupTime = now;
112
+ this.isInitedAlarm = true;
113
+ }
114
+ if (!this.isInitedSchema) {
115
+ const initTableResult = this.ctx.storage.sql.exec(`
116
+ CREATE TABLE IF NOT EXISTS "${this.schemaPrefix}events" (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ payload TEXT NOT NULL,
119
+ stored_at INTEGER NOT NULL DEFAULT (unixepoch())
120
+ )
121
+ `);
122
+ this.ctx.storage.sql.exec(`
123
+ CREATE INDEX IF NOT EXISTS "${this.schemaPrefix}idx_events_id" ON "${this.schemaPrefix}events" (id)
124
+ `);
110
125
  this.ctx.storage.sql.exec(`
111
- DELETE FROM "${this.schemaPrefix}events" WHERE stored_at < unixepoch() - ?
112
- `, this.retentionSeconds);
126
+ CREATE INDEX IF NOT EXISTS "${this.schemaPrefix}idx_events_stored_at" ON "${this.schemaPrefix}events" (stored_at)
127
+ `);
128
+ this.isInitedSchema = true;
129
+ if (initTableResult.rowsWritten > 0) {
130
+ this.lastCleanupTime = Date.now();
131
+ }
132
+ }
133
+ const now = Date.now();
134
+ if (this.lastCleanupTime && this.lastCleanupTime + this.retentionSeconds * 1e3 > now) {
113
135
  return;
114
136
  }
115
- const result = this.ctx.storage.sql.exec(`
116
- CREATE TABLE IF NOT EXISTS "${this.schemaPrefix}events" (
117
- id INTEGER PRIMARY KEY AUTOINCREMENT,
118
- payload TEXT NOT NULL,
119
- stored_at INTEGER NOT NULL DEFAULT (unixepoch())
120
- )
121
- `);
137
+ this.lastCleanupTime = now;
122
138
  this.ctx.storage.sql.exec(`
123
- CREATE INDEX IF NOT EXISTS "${this.schemaPrefix}idx_events_id" ON "${this.schemaPrefix}events" (id)
124
- `);
125
- this.ctx.storage.sql.exec(`
126
- CREATE INDEX IF NOT EXISTS "${this.schemaPrefix}idx_events_stored_at" ON "${this.schemaPrefix}events" (stored_at)
127
- `);
128
- this.isInitedSchema = true;
129
- if (!this.isInitedAlarm || result.rowsWritten > 0) {
130
- await this.scheduleAlarm();
131
- this.isInitedAlarm = true;
132
- }
139
+ DELETE FROM "${this.schemaPrefix}events" WHERE stored_at < unixepoch() - ?
140
+ `, this.retentionSeconds);
133
141
  }
134
- async resetData() {
142
+ async resetSchema() {
135
143
  this.ctx.storage.sql.exec(`DROP TABLE IF EXISTS "${this.schemaPrefix}events"`);
136
144
  this.isInitedSchema = false;
137
- await this.ensureReady();
145
+ await this.ensureSchemaAndCleanup();
138
146
  }
139
147
  scheduleAlarm() {
140
148
  return this.ctx.storage.setAlarm(Date.now() + (this.retentionSeconds + this.inactiveDataRetentionTime) * 1e3);
@@ -205,10 +213,10 @@ class DurablePublisher extends Publisher {
205
213
  data: { json: json_, meta: meta_ },
206
214
  meta: getEventMeta(payload)
207
215
  };
208
- const response = await stub.fetch(new Request("http://localhost/publish", {
216
+ const response = await stub.fetch("http://localhost/publish", {
209
217
  method: "POST",
210
218
  body: stringifyJSON(serialized)
211
- }));
219
+ });
212
220
  if (!response.ok) {
213
221
  throw new Error(`Failed to publish event: ${response.status} ${response.statusText}`, {
214
222
  cause: response
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/experimental-publisher-durable-object",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -26,8 +26,8 @@
26
26
  "dependencies": {
27
27
  "@orpc/client": "1.11.3",
28
28
  "@orpc/experimental-publisher": "1.11.3",
29
- "@orpc/shared": "1.11.3",
30
- "@orpc/standard-server": "1.11.3"
29
+ "@orpc/standard-server": "1.11.3",
30
+ "@orpc/shared": "1.11.3"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@cloudflare/workers-types": "^4.20251118.0",