@lde/distribution-monitor 0.1.13 → 0.1.14
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/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +40 -6
- package/package.json +1 -1
package/dist/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
1
2
|
import type { ObservationStore, Observation } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* PostgreSQL implementation of the ObservationStore interface.
|
|
@@ -24,4 +25,26 @@ export declare class PostgresObservationStore implements ObservationStore {
|
|
|
24
25
|
store(observation: Omit<Observation, 'id'>): Promise<Observation>;
|
|
25
26
|
refreshLatestObservationsView(): Promise<void>;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Create the unique index that `REFRESH ... CONCURRENTLY` requires on the
|
|
30
|
+
* `latest_observations` materialized view. drizzle's `pgMaterializedView` does
|
|
31
|
+
* not model it, so `pushSchema` never emits it; create it idempotently here —
|
|
32
|
+
* `IF NOT EXISTS` matches by name and skips (no rebuild) when it already exists.
|
|
33
|
+
*
|
|
34
|
+
* The statement takes a SHARE lock on the view, which conflicts with the
|
|
35
|
+
* EXCLUSIVE lock held by a `REFRESH ... CONCURRENTLY` running in another
|
|
36
|
+
* instance. During a rolling deploy the old and new pods overlap, so that wait
|
|
37
|
+
* can exceed `lock_timeout` and raise `55P03` (lock_not_available). Tolerate it:
|
|
38
|
+
* whenever the view is being refreshed the index already exists, so a failed
|
|
39
|
+
* re-check is harmless — far better than aborting startup and crash-looping the
|
|
40
|
+
* monitor. Re-throw anything else.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ensureLatestObservationsIndex(db: Pick<PostgresJsDatabase, 'execute'>): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether an error is (or wraps) the PostgreSQL `lock_not_available` (`55P03`)
|
|
45
|
+
* SQLSTATE, raised when a statement exceeds `lock_timeout` waiting for a
|
|
46
|
+
* contended lock. drizzle wraps the driver error, so the SQLSTATE lives on a
|
|
47
|
+
* nested `cause` rather than the top-level error.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isLockNotAvailable(error: unknown): boolean;
|
|
27
50
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgBhE;;GAEG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,OAAO,CAAC,EAAE,CAAqB;IAE/B,OAAO;IAYP;;;;;;;;;;;OAWG;WACU,MAAM,CACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,wBAAwB,CAAC;IAsC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAK9C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAS5C,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjE,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAU1D"}
|
package/dist/store.js
CHANGED
|
@@ -62,12 +62,7 @@ export class PostgresObservationStore {
|
|
|
62
62
|
for (const statement of sqlStatements) {
|
|
63
63
|
await store.db.execute(sql.raw(statement));
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
// REFRESH ... CONCURRENTLY but is not modelled by drizzle's
|
|
67
|
-
// `pgMaterializedView`, so `pushSchema` never emits it. Create it
|
|
68
|
-
// idempotently — `IF NOT EXISTS` matches by name and skips (no rebuild) when
|
|
69
|
-
// it already exists.
|
|
70
|
-
await store.db.execute(sql `CREATE UNIQUE INDEX IF NOT EXISTS latest_observations_monitor_idx ON latest_observations (monitor)`);
|
|
65
|
+
await ensureLatestObservationsIndex(store.db);
|
|
71
66
|
return store;
|
|
72
67
|
}
|
|
73
68
|
async close() {
|
|
@@ -97,3 +92,42 @@ export class PostgresObservationStore {
|
|
|
97
92
|
await this.db.execute(refreshLatestObservationsViewSql);
|
|
98
93
|
}
|
|
99
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Create the unique index that `REFRESH ... CONCURRENTLY` requires on the
|
|
97
|
+
* `latest_observations` materialized view. drizzle's `pgMaterializedView` does
|
|
98
|
+
* not model it, so `pushSchema` never emits it; create it idempotently here —
|
|
99
|
+
* `IF NOT EXISTS` matches by name and skips (no rebuild) when it already exists.
|
|
100
|
+
*
|
|
101
|
+
* The statement takes a SHARE lock on the view, which conflicts with the
|
|
102
|
+
* EXCLUSIVE lock held by a `REFRESH ... CONCURRENTLY` running in another
|
|
103
|
+
* instance. During a rolling deploy the old and new pods overlap, so that wait
|
|
104
|
+
* can exceed `lock_timeout` and raise `55P03` (lock_not_available). Tolerate it:
|
|
105
|
+
* whenever the view is being refreshed the index already exists, so a failed
|
|
106
|
+
* re-check is harmless — far better than aborting startup and crash-looping the
|
|
107
|
+
* monitor. Re-throw anything else.
|
|
108
|
+
*/
|
|
109
|
+
export async function ensureLatestObservationsIndex(db) {
|
|
110
|
+
try {
|
|
111
|
+
await db.execute(sql `CREATE UNIQUE INDEX IF NOT EXISTS latest_observations_monitor_idx ON latest_observations (monitor)`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!isLockNotAvailable(error)) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Whether an error is (or wraps) the PostgreSQL `lock_not_available` (`55P03`)
|
|
121
|
+
* SQLSTATE, raised when a statement exceeds `lock_timeout` waiting for a
|
|
122
|
+
* contended lock. drizzle wraps the driver error, so the SQLSTATE lives on a
|
|
123
|
+
* nested `cause` rather than the top-level error.
|
|
124
|
+
*/
|
|
125
|
+
export function isLockNotAvailable(error) {
|
|
126
|
+
if (typeof error !== 'object' || error === null) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if ('code' in error && error.code === '55P03') {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return ('cause' in error && isLockNotAvailable(error.cause));
|
|
133
|
+
}
|
package/package.json
CHANGED