@pg-prime/events 0.0.0-stage → 0.2.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.
Files changed (60) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +43 -2
  3. package/dist/channel.d.ts +19 -0
  4. package/dist/channel.d.ts.map +1 -0
  5. package/dist/channel.js +51 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/define.d.ts +49 -0
  8. package/dist/define.d.ts.map +1 -0
  9. package/dist/define.js +39 -0
  10. package/dist/define.js.map +1 -0
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +30 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/events.d.ts +57 -0
  16. package/dist/events.d.ts.map +1 -0
  17. package/dist/events.js +99 -0
  18. package/dist/events.js.map +1 -0
  19. package/dist/hooks.d.ts +40 -0
  20. package/dist/hooks.d.ts.map +1 -0
  21. package/dist/hooks.js +27 -0
  22. package/dist/hooks.js.map +1 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +6 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/log/cursor.d.ts +37 -0
  28. package/dist/log/cursor.d.ts.map +1 -0
  29. package/dist/log/cursor.js +58 -0
  30. package/dist/log/cursor.js.map +1 -0
  31. package/dist/log/migrations.d.ts +3 -0
  32. package/dist/log/migrations.d.ts.map +1 -0
  33. package/dist/log/migrations.js +55 -0
  34. package/dist/log/migrations.js.map +1 -0
  35. package/dist/log/sql.d.ts +68 -0
  36. package/dist/log/sql.d.ts.map +1 -0
  37. package/dist/log/sql.js +251 -0
  38. package/dist/log/sql.js.map +1 -0
  39. package/dist/log/subscription.d.ts +53 -0
  40. package/dist/log/subscription.d.ts.map +1 -0
  41. package/dist/log/subscription.js +272 -0
  42. package/dist/log/subscription.js.map +1 -0
  43. package/dist/log/topic.d.ts +48 -0
  44. package/dist/log/topic.d.ts.map +1 -0
  45. package/dist/log/topic.js +111 -0
  46. package/dist/log/topic.js.map +1 -0
  47. package/dist/payload.d.ts +40 -0
  48. package/dist/payload.d.ts.map +1 -0
  49. package/dist/payload.js +53 -0
  50. package/dist/payload.js.map +1 -0
  51. package/dist/unsupported-typescript.d.ts +70 -0
  52. package/dist/util.d.ts +11 -0
  53. package/dist/util.d.ts.map +1 -0
  54. package/dist/util.js +31 -0
  55. package/dist/util.js.map +1 -0
  56. package/dist/waker.d.ts +22 -0
  57. package/dist/waker.d.ts.map +1 -0
  58. package/dist/waker.js +86 -0
  59. package/dist/waker.js.map +1 -0
  60. package/package.json +53 -3
@@ -0,0 +1,48 @@
1
+ import type { Queryable, Subscription } from 'pg-prime';
2
+ import type { TopicDef } from '../define.js';
3
+ import type { Core } from '../events.js';
4
+ import type { EntryRow } from './sql.js';
5
+ export interface Entry<T> {
6
+ readonly id: bigint;
7
+ readonly topic: string;
8
+ readonly type: string | null;
9
+ readonly key: string | null;
10
+ readonly payload: T;
11
+ readonly headers: Record<string, string> | null;
12
+ readonly at: Date;
13
+ /** The opaque cursor state right after this entry: the only valid resume handle. */
14
+ readonly cursor: string;
15
+ }
16
+ export interface PublishOptions {
17
+ tx?: Queryable<any>;
18
+ type?: string;
19
+ key?: string;
20
+ headers?: Record<string, string>;
21
+ }
22
+ export interface TopicHandle<T> {
23
+ publish(payload: T, opts?: PublishOptions): Promise<{
24
+ id: bigint;
25
+ }>;
26
+ publishMany(items: readonly ({
27
+ payload: T;
28
+ } & Omit<PublishOptions, 'tx'>)[], opts?: {
29
+ tx?: Queryable<any>;
30
+ }): Promise<{
31
+ id: bigint;
32
+ }[]>;
33
+ tail(handler: (e: Entry<T>) => void, opts?: {
34
+ from?: 'now' | {
35
+ after: string;
36
+ };
37
+ signal?: AbortSignal;
38
+ pollMs?: number;
39
+ }): Promise<Subscription>;
40
+ /** Browse by id for admin tools; not a resume handle. */
41
+ read(opts?: {
42
+ afterId?: bigint;
43
+ limit?: number;
44
+ }): Promise<Omit<Entry<T>, 'cursor'>[]>;
45
+ }
46
+ export declare function entryOf<T>(def: TopicDef<T>, r: EntryRow): Promise<Omit<Entry<T>, 'cursor'>>;
47
+ export declare function topicHandle<T>(core: Core, def: TopicDef<T>): TopicHandle<T>;
48
+ //# sourceMappingURL=topic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic.d.ts","sourceRoot":"","sources":["../../src/log/topic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAKxC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;IAC/C,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;IACjB,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnE,WAAW,CACT,KAAK,EAAE,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,EAAE,EAC/D,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;KAAE,GAC7B,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAA;IAC5B,IAAI,CACF,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAC9B,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,KAAK,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GACjF,OAAO,CAAC,YAAY,CAAC,CAAA;IACxB,yDAAyD;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;CACvF;AAED,wBAAsB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAUjG;AAID,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAiG3E"}
@@ -0,0 +1,111 @@
1
+ import { decodePayload, encodePayload } from '../payload.js';
2
+ import { ASYNC_DISPOSE, emitAll } from '../util.js';
3
+ import { waker } from '../waker.js';
4
+ import { advance, advanceTo, decodeCursor, encodeCursor, seed } from './cursor.js';
5
+ export async function entryOf(def, r) {
6
+ return {
7
+ id: r.id,
8
+ topic: def.name,
9
+ type: r.type,
10
+ key: r.key,
11
+ payload: await decodePayload(def.payload, r.payload),
12
+ headers: r.headers,
13
+ at: r.at,
14
+ };
15
+ }
16
+ const TAIL_BATCH = 500;
17
+ export function topicHandle(core, def) {
18
+ const channel = core.topicChannel(def.name);
19
+ const handle = {
20
+ async publish(payload, opts = {}) {
21
+ const { tx, ...item } = opts;
22
+ const [r] = await handle.publishMany([{ payload, ...item }], tx === undefined ? {} : { tx });
23
+ return r;
24
+ },
25
+ async publishMany(items, opts = {}) {
26
+ if (opts.tx === undefined)
27
+ await core.ready(def);
28
+ else
29
+ core.assertStarted('publish');
30
+ if (items.length === 0)
31
+ return [];
32
+ const rows = await Promise.all(items.map(async (i) => ({
33
+ type: i.type ?? null,
34
+ key: i.key ?? null,
35
+ headers: i.headers ?? null,
36
+ payload: await encodePayload(def.payload, i.payload),
37
+ })));
38
+ const ids = await core.sql.publish(opts.tx ?? core.db, def.name, channel, rows);
39
+ core.hooks.emit('onPublish', {
40
+ topic: def.name,
41
+ count: ids.length,
42
+ inTx: opts.tx !== undefined,
43
+ });
44
+ return ids.map((id) => ({ id }));
45
+ },
46
+ async tail(handler, opts = {}) {
47
+ await core.ready(def);
48
+ const from = opts.from ?? 'now';
49
+ let cursor = from === 'now'
50
+ ? await core.sql.seedNow(core.db).then((s) => seed(s.xmax, s.xip))
51
+ : decodeCursor(from.after);
52
+ const errors = new Set();
53
+ const fail = (e) => emitAll(errors, e);
54
+ const stop = new AbortController();
55
+ opts.signal?.addEventListener('abort', () => stop.abort(), { once: true });
56
+ if (opts.signal?.aborted === true)
57
+ stop.abort();
58
+ const wake = waker(core.db, channel, {
59
+ pollMs: opts.pollMs ?? 5000,
60
+ signal: stop.signal,
61
+ onInternal: (e) => core.hooks.emit('onInternal', e),
62
+ });
63
+ const loop = (async () => {
64
+ do {
65
+ try {
66
+ while (!stop.signal.aborted) {
67
+ const { view, rows } = await core.sql.read(core.db, def.name, cursor, TAIL_BATCH, null);
68
+ for (const r of rows) {
69
+ cursor = advance(cursor, view, r);
70
+ try {
71
+ handler({ ...(await entryOf(def, r)), cursor: encodeCursor(cursor) });
72
+ }
73
+ catch (e) {
74
+ fail(e);
75
+ }
76
+ }
77
+ cursor = advanceTo(cursor, view);
78
+ if (view.pos === null && view.late === null)
79
+ break;
80
+ }
81
+ }
82
+ catch (e) {
83
+ fail(e);
84
+ }
85
+ } while ((await wake.wait()) !== undefined);
86
+ })();
87
+ const close = async () => {
88
+ stop.abort();
89
+ await loop;
90
+ core.running.delete(sub);
91
+ };
92
+ // Tails heal gaps from the log, so only 'error' ever fires.
93
+ const on = (event, h) => {
94
+ if (event !== 'error')
95
+ return () => { };
96
+ errors.add(h);
97
+ return () => void errors.delete(h);
98
+ };
99
+ const sub = { channel, close, [ASYNC_DISPOSE]: close, on };
100
+ core.running.add(sub);
101
+ return sub;
102
+ },
103
+ async read(opts = {}) {
104
+ await core.ready(def);
105
+ const rows = await core.sql.browse(core.db, def.name, opts.afterId ?? 0n, opts.limit ?? 100);
106
+ return Promise.all(rows.map((r) => entryOf(def, r)));
107
+ },
108
+ };
109
+ return handle;
110
+ }
111
+ //# sourceMappingURL=topic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic.js","sourceRoot":"","sources":["../../src/log/topic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /** The Standard Schema v1 interface (standardschema.dev), copied as a type: no dependency. */
2
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
3
+ readonly '~standard': {
4
+ readonly version: 1;
5
+ readonly vendor: string;
6
+ readonly validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>;
7
+ readonly types?: {
8
+ readonly input: Input;
9
+ readonly output: Output;
10
+ } | undefined;
11
+ };
12
+ }
13
+ export declare namespace StandardSchemaV1 {
14
+ type Result<Output> = {
15
+ readonly value: Output;
16
+ readonly issues?: undefined;
17
+ } | {
18
+ readonly issues: readonly Issue[];
19
+ };
20
+ interface Issue {
21
+ readonly message: string;
22
+ readonly path?: readonly (PropertyKey | {
23
+ readonly key: PropertyKey;
24
+ })[] | undefined;
25
+ }
26
+ }
27
+ export interface PayloadOptions<T> {
28
+ /** Validates on both sides: before encoding and after decoding. */
29
+ schema?: StandardSchemaV1<unknown, T>;
30
+ /** Maps the value to and from its JSON form. */
31
+ codec?: {
32
+ encode(v: T): unknown;
33
+ decode(v: unknown): T;
34
+ };
35
+ }
36
+ /** Validates, encodes and serializes a payload; refuses NUL characters and oversize documents. */
37
+ export declare function encodePayload<T>(opts: PayloadOptions<T>, value: T, maxBytes?: number): Promise<string>;
38
+ /** Decodes a JSON value read back from the database and validates it. */
39
+ export declare function decodePayload<T>(opts: PayloadOptions<T>, wire: unknown): Promise<T>;
40
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"AAEA,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAC/D,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;QACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,OAAO,KACX,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KAChF,CAAA;CACF;AAED,MAAM,CAAC,OAAO,WAAW,gBAAgB,CAAC;IACxC,KAAK,MAAM,CAAC,MAAM,IACd;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,GACvD;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAA;KAAE,CAAA;IACzC,UAAU,KAAK;QACb,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG;YAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;SAAE,CAAC,EAAE,GAAG,SAAS,CAAA;KACrF;CACF;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,mEAAmE;IACnE,MAAM,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACrC,gDAAgD;IAChD,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;QAAC,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAA;KAAE,CAAA;CACzD;AAID,kGAAkG;AAClG,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,KAAK,EAAE,CAAC,EACR,QAAQ,GAAE,MAAkB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAEnF"}
@@ -0,0 +1,53 @@
1
+ import { PayloadError } from './errors.js';
2
+ const MAX_BYTES = 1024 * 1024;
3
+ /** Validates, encodes and serializes a payload; refuses NUL characters and oversize documents. */
4
+ export async function encodePayload(opts, value, maxBytes = MAX_BYTES) {
5
+ const v = await validate(opts, value);
6
+ const wire = opts.codec === undefined ? v : opts.codec.encode(v);
7
+ const json = JSON.stringify(wire);
8
+ if (json === undefined)
9
+ throw new PayloadError('@pg-prime/events: a payload must be JSON.');
10
+ const path = nulPath(wire, []);
11
+ if (path !== undefined) {
12
+ throw new PayloadError(`@pg-prime/events: the payload has a NUL character at ${fmt(path)}; jsonb cannot store it.`, path);
13
+ }
14
+ const bytes = new TextEncoder().encode(json).length;
15
+ if (bytes > maxBytes) {
16
+ throw new PayloadError(`@pg-prime/events: the payload is ${bytes} bytes; the limit is ${maxBytes}. Store the ` +
17
+ `document in a table and publish its id.`);
18
+ }
19
+ return json;
20
+ }
21
+ /** Decodes a JSON value read back from the database and validates it. */
22
+ export function decodePayload(opts, wire) {
23
+ return validate(opts, opts.codec === undefined ? wire : opts.codec.decode(wire));
24
+ }
25
+ async function validate(opts, value) {
26
+ if (opts.schema === undefined)
27
+ return value;
28
+ const r = await opts.schema['~standard'].validate(value);
29
+ if (r.issues === undefined)
30
+ return r.value;
31
+ const issue = r.issues[0];
32
+ const path = (issue?.path ?? []).map((p) => (typeof p === 'object' ? p.key : p));
33
+ throw new PayloadError(`@pg-prime/events: the payload failed its schema at ${fmt(path)}: ${issue?.message ?? 'invalid'}`, path);
34
+ }
35
+ function nulPath(v, path) {
36
+ if (typeof v === 'string')
37
+ return v.includes('\0') ? path : undefined;
38
+ if (typeof v !== 'object' || v === null)
39
+ return undefined;
40
+ for (const [k, x] of Object.entries(v)) {
41
+ const key = Array.isArray(v) ? Number(k) : k;
42
+ if (k.includes('\0'))
43
+ return [...path, key];
44
+ const p = nulPath(x, [...path, key]);
45
+ if (p !== undefined)
46
+ return p;
47
+ }
48
+ return undefined;
49
+ }
50
+ function fmt(path) {
51
+ return path.length === 0 ? '(root)' : path.map(String).join('.');
52
+ }
53
+ //# sourceMappingURL=payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ // GENERATED by tools/api-snapshot.mjs — do not edit by hand.
2
+ //
3
+ // The `types@<5.9` gate of design/08 §2.2, first in every condition object of every subpath in
4
+ // package.json. A consumer on TypeScript < 5.9 resolves `@pg-prime/events` to THIS file instead of the
5
+ // real declarations, so every name still exists and every USE of one lands the message in the
6
+ // error text. Verified against TypeScript 5.8.3 by tools/pack-smoke.mjs on every run:
7
+ //
8
+ // consumer.ts(12,15): error TS2349: This expression is not callable.
9
+ // Type '{ ERROR: "@pg-prime/events requires TypeScript >= 5.9 — see https://pg-prime.dev/ts"; }' has no call signatures.
10
+ //
11
+ // Naming the key `ERROR` rather than a `__dunder__` is §2.2's own refinement over Kysely's
12
+ // version: it reads first in a truncated hover.
13
+ //
14
+ // ## `Unsupported<M = …> = M` is an identity alias ON PURPOSE — do not "simplify" it
15
+ //
16
+ // Written the obvious way, `type Unsupported = { ERROR: '…' }`, TypeScript prints the ALIAS NAME
17
+ // in the diagnostic — "Type 'Unsupported' has no call signatures" — and the message never
18
+ // reaches the user, which defeats the entire file. A generic alias whose body is just its own
19
+ // parameter has no alias symbol to print, so the checker falls back to the structural form and
20
+ // the sentence appears. Measured, both shapes, on 5.9.3 and 5.8.3.
21
+ //
22
+ // The lists are the root entry's value and type exports, taken from tools/api-snapshot/*.json.
23
+ // `node tools/api-snapshot.mjs --check` fails if this file and that golden disagree, which is
24
+ // what stops the stub from silently rotting into "this package has no exported member 'x'".
25
+ // The type parameters are optional and are only as many as the real declaration has, so that a
26
+ // generic use resolves to the brand instead of erroring with "is not generic".
27
+
28
+ type Unsupported<Message = { ERROR: '@pg-prime/events requires TypeScript >= 5.9 — see https://pg-prime.dev/ts' }> = Message
29
+
30
+ export declare const EventsLeaseLostError: Unsupported
31
+ export declare const NotStartedError: Unsupported
32
+ export declare const PayloadError: Unsupported
33
+ export declare const SchemaVersionError: Unsupported
34
+ export declare const defineChannel: Unsupported
35
+ export declare const defineSubscription: Unsupported
36
+ export declare const defineTopic: Unsupported
37
+ export declare const engineChannel: Unsupported
38
+ export declare const engineChannelSql: Unsupported
39
+ export declare const pgEvents: Unsupported
40
+ export declare const waker: Unsupported
41
+
42
+ export type BatchHandler<T0 = any> = Unsupported
43
+ export type ChannelDef<T0 = any> = Unsupported
44
+ export type ChannelHandle<T0 = any> = Unsupported
45
+ export type Entry<T0 = any> = Unsupported
46
+ export type EntryHandler<T0 = any> = Unsupported
47
+ export type Events<T0 = any> = Unsupported
48
+ export type EventsConfig<T0 = any> = Unsupported
49
+ export type EventsDefinitions = Unsupported
50
+ export type EventsHooks = Unsupported
51
+ export type EventsLeaseLostError = Unsupported
52
+ export type InternalKind = Unsupported
53
+ export type NotStartedError = Unsupported
54
+ export type PayloadError = Unsupported
55
+ export type PayloadOf<T0 = any> = Unsupported
56
+ export type PayloadOptions<T0 = any> = Unsupported
57
+ export type PublishOptions = Unsupported
58
+ export type ReadContext = Unsupported
59
+ export type Reader = Unsupported
60
+ export type RetryOptions = Unsupported
61
+ export type RunOptions = Unsupported
62
+ export type SchemaVersionError = Unsupported
63
+ export type StandardSchemaV1<T0 = any, T1 = any> = Unsupported
64
+ export type SubscriptionDef<T0 = any> = Unsupported
65
+ export type SubscriptionHandle<T0 = any> = Unsupported
66
+ export type TopicDef<T0 = any> = Unsupported
67
+ export type TopicHandle<T0 = any> = Unsupported
68
+ export type WakeReason = Unsupported
69
+ export type Waker = Unsupported
70
+ export type WakerOptions = Unsupported
package/dist/util.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare const ASYNC_DISPOSE: symbol;
2
+ /** Calls every observer; a throwing one neither stops the rest nor escapes. */
3
+ export declare function emitAll(observers: Iterable<(e: unknown) => void>, e: unknown): void;
4
+ /** Resolves after `ms`, or early when `signal` aborts. */
5
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
6
+ export declare function backoffMs(p: {
7
+ baseDelayMs: number;
8
+ maxDelayMs: number;
9
+ jitter: 'full' | 'equal' | 'none';
10
+ }, attempt: number, random?: () => number): number;
11
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,EAAE,MAC6D,CAAA;AAEzF,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,CAMnF;AAED,0DAA0D;AAC1D,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE;AAED,wBAAgB,SAAS,CACvB,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;CAAE,EACjF,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,MAAoB,GACjC,MAAM,CAIR"}
package/dist/util.js ADDED
@@ -0,0 +1,31 @@
1
+ export const ASYNC_DISPOSE = Symbol.asyncDispose ?? Symbol.for('Symbol.asyncDispose');
2
+ /** Calls every observer; a throwing one neither stops the rest nor escapes. */
3
+ export function emitAll(observers, e) {
4
+ for (const h of observers) {
5
+ try {
6
+ h(e);
7
+ }
8
+ catch { }
9
+ }
10
+ }
11
+ /** Resolves after `ms`, or early when `signal` aborts. */
12
+ export function sleep(ms, signal) {
13
+ return new Promise((resolve) => {
14
+ if (signal?.aborted === true)
15
+ return resolve();
16
+ const done = () => {
17
+ clearTimeout(t);
18
+ signal?.removeEventListener('abort', done);
19
+ resolve();
20
+ };
21
+ const t = setTimeout(done, ms);
22
+ signal?.addEventListener('abort', done, { once: true });
23
+ });
24
+ }
25
+ export function backoffMs(p, attempt, random = Math.random) {
26
+ const ceiling = Math.min(p.maxDelayMs, p.baseDelayMs * 2 ** Math.max(0, attempt - 1));
27
+ if (p.jitter === 'none')
28
+ return ceiling;
29
+ return p.jitter === 'equal' ? ceiling / 2 + random() * (ceiling / 2) : random() * ceiling;
30
+ }
31
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GACvB,MAAoC,CAAC,YAAY,IAAI,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;AAEzF,+EAA+E;AAC/E,MAAM,UAAU,OAAO,CAAC,SAAyC,EAAE,CAAU;IAC3E,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,CAAC,CAAC,CAAC,CAAC,CAAA;QACN,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;AACH,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,EAAE,CAAA;QAC9C,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,YAAY,CAAC,CAAC,CAAC,CAAA;YACf,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAC1C,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;QACD,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAC9B,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,CAAiF,EACjF,OAAe,EACf,MAAM,GAAiB,IAAI,CAAC,MAAM;IAElC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA;IACrF,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,OAAO,CAAA;IACvC,OAAO,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAA;AAC3F,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Db } from 'pg-prime';
2
+ import type { EventsHooks } from './hooks.js';
3
+ export type WakeReason = 'notify' | 'gap' | 'poll';
4
+ export interface WakerOptions {
5
+ pollMs: number;
6
+ signal?: AbortSignal;
7
+ /** Receives `listen-unavailable` when the waker falls back to polling. */
8
+ onInternal?: EventsHooks['onInternal'];
9
+ }
10
+ /** A single-use async iterable of wake reasons, plus `wait(ms)` for loops with their own timers. */
11
+ export interface Waker extends AsyncIterable<WakeReason> {
12
+ /** The next reason, waiting at most `ms` (default `pollMs`; a timeout is `'poll'`); `undefined` once closed. */
13
+ wait(ms?: number): Promise<WakeReason | undefined>;
14
+ close(): Promise<void>;
15
+ }
16
+ /**
17
+ * Wakes on a NOTIFY on `channel`, a LISTEN `gap`, or `pollMs` of silence. LISTEN starts here; once
18
+ * it is established the first reason is `'gap'`, since anything sent before it was missed. Behind
19
+ * a transaction pooler without `directConnection` it only polls.
20
+ */
21
+ export declare function waker(db: Db, channel: string, opts: WakerOptions): Waker;
22
+ //# sourceMappingURL=waker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waker.d.ts","sourceRoot":"","sources":["../src/waker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAgB,MAAM,UAAU,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAElD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;CACvC;AAED,oGAAoG;AACpG,MAAM,WAAW,KAAM,SAAQ,aAAa,CAAC,UAAU,CAAC;IACtD,gHAAgH;IAChH,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAA;IAClD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,KAAK,CA0ExE"}
package/dist/waker.js ADDED
@@ -0,0 +1,86 @@
1
+ import { UnsupportedInPoolerModeError } from 'pg-prime';
2
+ /**
3
+ * Wakes on a NOTIFY on `channel`, a LISTEN `gap`, or `pollMs` of silence. LISTEN starts here; once
4
+ * it is established the first reason is `'gap'`, since anything sent before it was missed. Behind
5
+ * a transaction pooler without `directConnection` it only polls.
6
+ */
7
+ export function waker(db, channel, opts) {
8
+ let sub;
9
+ let listening;
10
+ let unsupported = false;
11
+ let reported = false;
12
+ let flag;
13
+ let wake;
14
+ let closed = opts.signal?.aborted === true;
15
+ const raise = (kind) => {
16
+ if (kind === 'gap' || flag === undefined)
17
+ flag = kind;
18
+ wake?.();
19
+ };
20
+ const listen = async () => {
21
+ try {
22
+ const s = await db.listen(channel, () => raise('notify'));
23
+ if (closed)
24
+ return void (await s.close());
25
+ s.on('gap', () => raise('gap'));
26
+ sub = s;
27
+ reported = false;
28
+ raise('gap');
29
+ }
30
+ catch (e) {
31
+ unsupported = e instanceof UnsupportedInPoolerModeError;
32
+ if (reported)
33
+ return;
34
+ reported = true;
35
+ opts.onInternal?.({
36
+ kind: 'listen-unavailable',
37
+ message: `@pg-prime/events: cannot LISTEN on ${channel}; polling every ${opts.pollMs} ms.`,
38
+ cause: e,
39
+ });
40
+ }
41
+ };
42
+ const close = async () => {
43
+ closed = true;
44
+ wake?.();
45
+ const s = sub;
46
+ sub = undefined;
47
+ await s?.close();
48
+ };
49
+ const wait = async (ms = opts.pollMs) => {
50
+ if (sub === undefined && !unsupported && !closed) {
51
+ listening ??= listen();
52
+ await listening;
53
+ listening = undefined;
54
+ }
55
+ if (flag === undefined && !closed) {
56
+ await new Promise((resolve) => {
57
+ const t = setTimeout(resolve, ms);
58
+ wake = () => {
59
+ clearTimeout(t);
60
+ resolve();
61
+ };
62
+ });
63
+ wake = undefined;
64
+ }
65
+ if (closed)
66
+ return undefined;
67
+ const reason = flag ?? 'poll';
68
+ flag = undefined;
69
+ return reason;
70
+ };
71
+ opts.signal?.addEventListener('abort', () => void close(), { once: true });
72
+ if (!closed)
73
+ listening = listen();
74
+ const it = {
75
+ next: async () => {
76
+ const value = await wait();
77
+ return value === undefined ? { done: true, value: undefined } : { done: false, value };
78
+ },
79
+ return: async () => {
80
+ await close();
81
+ return { done: true, value: undefined };
82
+ },
83
+ };
84
+ return { wait, close, [Symbol.asyncIterator]: () => it };
85
+ }
86
+ //# sourceMappingURL=waker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waker.js","sourceRoot":"","sources":["../src/waker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@pg-prime/events",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Typed LISTEN/NOTIFY channels, a durable event log with subscriptions, and tails for pg-prime. Pre-alpha — the API will change.",
5
+ "keywords": [
6
+ "event-log",
7
+ "events",
8
+ "listen-notify",
9
+ "pg-prime",
10
+ "postgres",
11
+ "postgresql",
12
+ "pubsub"
13
+ ],
14
+ "license": "MPL-2.0",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/pg-prime/pg-prime.git"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "exports": {
25
+ ".": {
26
+ "types@<5.9": "./dist/unsupported-typescript.d.ts",
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true
35
+ },
36
+ "devDependencies": {
37
+ "fast-check": "^4.10.2",
38
+ "vitest": "^4.1.10",
39
+ "@pg-prime/testing": "0.1.1",
40
+ "pg-prime": "0.2.0"
41
+ },
42
+ "peerDependencies": {
43
+ "pg-prime": ">=0.2.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.12"
47
+ },
48
+ "scripts": {
49
+ "build": "node ../../tools/build-package.mjs",
50
+ "clean": "node ../../tools/build-package.mjs --clean",
51
+ "typecheck": "tsc --noEmit -p tsconfig.json",
52
+ "test": "vitest run --project unit",
53
+ "test:live": "vitest run --project unit --project live",
54
+ "test:pg": "vitest run"
55
+ }
6
56
  }