hazo_notify 5.2.1 → 6.0.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.
- package/README.md +38 -6
- package/config/hazo_notify_config.ini +5 -0
- package/dist/lib/adapters/email/config.d.ts.map +1 -1
- package/dist/lib/adapters/email/config.js +7 -6
- package/dist/lib/adapters/email/config.js.map +1 -1
- package/dist/lib/adapters/email/providers/zeptomail_provider.d.ts.map +1 -1
- package/dist/lib/adapters/email/providers/zeptomail_provider.js +11 -19
- package/dist/lib/adapters/email/providers/zeptomail_provider.js.map +1 -1
- package/dist/lib/adapters/email/utils/logger.d.ts +4 -11
- package/dist/lib/adapters/email/utils/logger.d.ts.map +1 -1
- package/dist/lib/adapters/email/utils/logger.js +11 -53
- package/dist/lib/adapters/email/utils/logger.js.map +1 -1
- package/dist/lib/adapters/telegram/config.d.ts.map +1 -1
- package/dist/lib/adapters/telegram/config.js +5 -4
- package/dist/lib/adapters/telegram/config.js.map +1 -1
- package/dist/lib/adapters/telegram/transport.d.ts.map +1 -1
- package/dist/lib/adapters/telegram/transport.js +2 -1
- package/dist/lib/adapters/telegram/transport.js.map +1 -1
- package/dist/lib/config/hazo_notify_config.d.ts +45 -0
- package/dist/lib/config/hazo_notify_config.d.ts.map +1 -0
- package/dist/lib/config/hazo_notify_config.js +82 -0
- package/dist/lib/config/hazo_notify_config.js.map +1 -0
- package/dist/lib/dispatcher/batch-key.js +2 -2
- package/dist/lib/dispatcher/batch-key.js.map +1 -1
- package/dist/lib/dispatcher/index.d.ts.map +1 -1
- package/dist/lib/dispatcher/index.js +6 -3
- package/dist/lib/dispatcher/index.js.map +1 -1
- package/dist/lib/errors.d.ts +30 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +57 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/inbox/connection.d.ts.map +1 -1
- package/dist/lib/inbox/connection.js +4 -3
- package/dist/lib/inbox/connection.js.map +1 -1
- package/dist/lib/inbox/storage.d.ts.map +1 -1
- package/dist/lib/inbox/storage.js +4 -3
- package/dist/lib/inbox/storage.js.map +1 -1
- package/dist/lib/inbox/types.d.ts +3 -1
- package/dist/lib/inbox/types.d.ts.map +1 -1
- package/dist/lib/inbox/worker/flush.d.ts.map +1 -1
- package/dist/lib/inbox/worker/flush.js +56 -50
- package/dist/lib/inbox/worker/flush.js.map +1 -1
- package/dist/lib/inbox/worker/render.d.ts.map +1 -1
- package/dist/lib/inbox/worker/render.js +2 -1
- package/dist/lib/inbox/worker/render.js.map +1 -1
- package/dist/lib/jobs/digest.d.ts +77 -0
- package/dist/lib/jobs/digest.d.ts.map +1 -0
- package/dist/lib/jobs/digest.js +127 -0
- package/dist/lib/jobs/digest.js.map +1 -0
- package/dist/lib/jobs/handler.d.ts.map +1 -1
- package/dist/lib/jobs/handler.js +23 -18
- package/dist/lib/jobs/handler.js.map +1 -1
- package/dist/lib/jobs/index.d.ts +2 -0
- package/dist/lib/jobs/index.d.ts.map +1 -1
- package/dist/lib/jobs/index.js +1 -0
- package/dist/lib/jobs/index.js.map +1 -1
- package/dist/lib/lifecycle/default_templates.d.ts.map +1 -1
- package/dist/lib/lifecycle/default_templates.js +36 -0
- package/dist/lib/lifecycle/default_templates.js.map +1 -1
- package/dist/lib/lifecycle/dispatch.d.ts.map +1 -1
- package/dist/lib/lifecycle/dispatch.js +2 -1
- package/dist/lib/lifecycle/dispatch.js.map +1 -1
- package/dist/lib/lifecycle/handler.d.ts.map +1 -1
- package/dist/lib/lifecycle/handler.js +103 -97
- package/dist/lib/lifecycle/handler.js.map +1 -1
- package/dist/lib/lifecycle/scheduler.d.ts.map +1 -1
- package/dist/lib/lifecycle/scheduler.js +2 -1
- package/dist/lib/lifecycle/scheduler.js.map +1 -1
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +3 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/preferences/storage.d.ts.map +1 -1
- package/dist/lib/preferences/storage.js +7 -2
- package/dist/lib/preferences/storage.js.map +1 -1
- package/dist/lib/preferences/types.d.ts +1 -1
- package/dist/lib/preferences/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +1 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +1 -5
- package/dist/lib/utils.js.map +1 -1
- package/migrations/011_digest_status.pg.sql +21 -0
- package/migrations/011_digest_status.sqlite.sql +43 -0
- package/migrations/012_preferences_digest.pg.sql +12 -0
- package/migrations/012_preferences_digest.sqlite.sql +51 -0
- package/package.json +20 -9
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
|
+
import { withContext, getContext } from 'hazo_core';
|
|
11
12
|
import { evaluateConditions } from './resolver.js';
|
|
13
|
+
import { NotifySequenceNotFoundError, NotifyStepNotFoundError } from '../errors.js';
|
|
12
14
|
import { dispatchStep } from './dispatch.js';
|
|
13
15
|
import { loadStatusRow, updateStatusRow } from './status.js';
|
|
14
16
|
/**
|
|
@@ -31,115 +33,119 @@ import { loadStatusRow, updateStatusRow } from './status.js';
|
|
|
31
33
|
export function createLifecycleStepHandler(opts, get_user_email) {
|
|
32
34
|
const max_attempts = opts.max_attempts ?? 3;
|
|
33
35
|
return async function lifecycle_step_handler(job, log) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
const correlationId = getContext()?.correlationId
|
|
37
|
+
?? `notify_lifecycle_${job.id}`;
|
|
38
|
+
return withContext({ correlationId }, async () => {
|
|
39
|
+
// hazo_jobs (SQLite backend) may deliver payload as a JSON string.
|
|
40
|
+
const payload = typeof job.payload === 'string'
|
|
41
|
+
? JSON.parse(job.payload)
|
|
42
|
+
: job.payload;
|
|
43
|
+
const { status_row_id, user_id, sequence_id, step_id } = payload;
|
|
44
|
+
log?.info?.(`[lifecycle] step start row=${status_row_id} user=${user_id} seq=${sequence_id} step=${step_id}`);
|
|
45
|
+
// 1. Load the status row.
|
|
46
|
+
const row = await loadStatusRow(opts.hazo_connect, status_row_id);
|
|
47
|
+
// 2. Guard: already processed or gone.
|
|
48
|
+
if (!row || row.status !== 'scheduled') {
|
|
49
|
+
log?.info?.(`[lifecycle] step skip (already handled) row=${status_row_id} status=${row?.status ?? 'not_found'}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// 3. Increment attempt counter.
|
|
53
|
+
const attempt_count = row.attempt_count + 1;
|
|
54
|
+
try {
|
|
55
|
+
// 4. Opt-out check.
|
|
56
|
+
if (opts.is_opted_out) {
|
|
57
|
+
const opted_out = await opts.is_opted_out(user_id);
|
|
58
|
+
if (opted_out) {
|
|
59
|
+
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
60
|
+
status: 'skipped',
|
|
61
|
+
skip_reason: 'opted_out',
|
|
62
|
+
attempt_count,
|
|
63
|
+
});
|
|
64
|
+
log?.info?.(`[lifecycle] step skipped (opted_out) row=${status_row_id}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 5. Locate the step definition.
|
|
69
|
+
const sequence = opts.sequences[sequence_id];
|
|
70
|
+
if (!sequence) {
|
|
71
|
+
throw new NotifySequenceNotFoundError(sequence_id);
|
|
72
|
+
}
|
|
73
|
+
const step = sequence.find((s) => s.id === step_id);
|
|
74
|
+
if (!step) {
|
|
75
|
+
throw new NotifyStepNotFoundError(sequence_id, step_id);
|
|
76
|
+
}
|
|
77
|
+
// 6. Evaluate conditions.
|
|
78
|
+
const condition_result = await evaluateConditions(step.conditions, opts.resolvers, user_id);
|
|
79
|
+
if (!condition_result.pass) {
|
|
80
|
+
const skip_reason = `condition_failed:${condition_result.reason ?? 'unknown'}`;
|
|
54
81
|
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
55
82
|
status: 'skipped',
|
|
56
|
-
skip_reason
|
|
83
|
+
skip_reason,
|
|
57
84
|
attempt_count,
|
|
58
85
|
});
|
|
59
|
-
log?.info?.(`[lifecycle] step skipped
|
|
86
|
+
log?.info?.(`[lifecycle] step skipped row=${status_row_id} reason=${skip_reason}`);
|
|
60
87
|
return;
|
|
61
88
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
// 7. Resolve recipient email address.
|
|
90
|
+
const to = await get_user_email(user_id);
|
|
91
|
+
// 8. Resolve extra template variables (e.g. opt-out link, A/B subjects).
|
|
92
|
+
const raw_vars = opts.get_variables
|
|
93
|
+
? await opts.get_variables(user_id, step_id)
|
|
94
|
+
: {};
|
|
95
|
+
// 8a. A/B subject selection: if both subject_a and subject_b are provided,
|
|
96
|
+
// pick one deterministically by hashing user_id + template_name.
|
|
97
|
+
// Remove both from template variables so they don't appear in the body.
|
|
98
|
+
let subject_override;
|
|
99
|
+
let ab_variant;
|
|
100
|
+
const { subject_a, subject_b, ...extra_vars } = raw_vars;
|
|
101
|
+
if (subject_a && subject_b) {
|
|
102
|
+
const hash_byte = createHash('sha256')
|
|
103
|
+
.update(user_id + step.template)
|
|
104
|
+
.digest()[0];
|
|
105
|
+
ab_variant = (hash_byte & 1) === 0 ? 'a' : 'b';
|
|
106
|
+
subject_override = ab_variant === 'a' ? subject_a : subject_b;
|
|
107
|
+
}
|
|
108
|
+
// 9. Dispatch the email.
|
|
109
|
+
await dispatchStep({
|
|
110
|
+
hazo_connect: opts.hazo_connect,
|
|
111
|
+
user_id,
|
|
112
|
+
template_name: step.template,
|
|
113
|
+
variables: extra_vars,
|
|
114
|
+
to,
|
|
115
|
+
locale: opts.default_locale ?? null,
|
|
116
|
+
subject_override,
|
|
80
117
|
});
|
|
81
|
-
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
// 7. Resolve recipient email address.
|
|
85
|
-
const to = await get_user_email(user_id);
|
|
86
|
-
// 8. Resolve extra template variables (e.g. opt-out link, A/B subjects).
|
|
87
|
-
const raw_vars = opts.get_variables
|
|
88
|
-
? await opts.get_variables(user_id, step_id)
|
|
89
|
-
: {};
|
|
90
|
-
// 8a. A/B subject selection: if both subject_a and subject_b are provided,
|
|
91
|
-
// pick one deterministically by hashing user_id + template_name.
|
|
92
|
-
// Remove both from template variables so they don't appear in the body.
|
|
93
|
-
let subject_override;
|
|
94
|
-
let ab_variant;
|
|
95
|
-
const { subject_a, subject_b, ...extra_vars } = raw_vars;
|
|
96
|
-
if (subject_a && subject_b) {
|
|
97
|
-
const hash_byte = createHash('sha256')
|
|
98
|
-
.update(user_id + step.template)
|
|
99
|
-
.digest()[0];
|
|
100
|
-
ab_variant = (hash_byte & 1) === 0 ? 'a' : 'b';
|
|
101
|
-
subject_override = ab_variant === 'a' ? subject_a : subject_b;
|
|
102
|
-
}
|
|
103
|
-
// 9. Dispatch the email.
|
|
104
|
-
await dispatchStep({
|
|
105
|
-
hazo_connect: opts.hazo_connect,
|
|
106
|
-
user_id,
|
|
107
|
-
template_name: step.template,
|
|
108
|
-
variables: extra_vars,
|
|
109
|
-
to,
|
|
110
|
-
locale: opts.default_locale ?? null,
|
|
111
|
-
subject_override,
|
|
112
|
-
});
|
|
113
|
-
// 10. Mark as sent (include dispatch_meta for A/B tracking).
|
|
114
|
-
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
115
|
-
status: 'sent',
|
|
116
|
-
sent_at: new Date().toISOString(),
|
|
117
|
-
attempt_count,
|
|
118
|
-
...(ab_variant
|
|
119
|
-
? { dispatch_meta: { ab_variant, subject: subject_override } }
|
|
120
|
-
: {}),
|
|
121
|
-
});
|
|
122
|
-
log?.info?.(`[lifecycle] step sent row=${status_row_id} template=${step.template}`);
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
-
log?.error?.(`[lifecycle] step error row=${status_row_id} err=${msg}`);
|
|
127
|
-
if (attempt_count >= max_attempts) {
|
|
118
|
+
// 10. Mark as sent (include dispatch_meta for A/B tracking).
|
|
128
119
|
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
129
|
-
status: '
|
|
130
|
-
|
|
120
|
+
status: 'sent',
|
|
121
|
+
sent_at: new Date().toISOString(),
|
|
131
122
|
attempt_count,
|
|
123
|
+
...(ab_variant
|
|
124
|
+
? { dispatch_meta: { ab_variant, subject: subject_override } }
|
|
125
|
+
: {}),
|
|
132
126
|
});
|
|
133
|
-
log?.
|
|
127
|
+
log?.info?.(`[lifecycle] step sent row=${status_row_id} template=${step.template}`);
|
|
134
128
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
129
|
+
catch (err) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
log?.error?.(`[lifecycle] step error row=${status_row_id} err=${msg}`);
|
|
132
|
+
if (attempt_count >= max_attempts) {
|
|
133
|
+
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
134
|
+
status: 'failed',
|
|
135
|
+
failure_message: msg,
|
|
136
|
+
attempt_count,
|
|
137
|
+
});
|
|
138
|
+
log?.error?.(`[lifecycle] step failed permanently row=${status_row_id}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Leave status as 'scheduled' so hazo_jobs retries the job.
|
|
142
|
+
await updateStatusRow(opts.hazo_connect, status_row_id, {
|
|
143
|
+
attempt_count,
|
|
144
|
+
failure_message: msg,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
141
147
|
}
|
|
142
|
-
}
|
|
148
|
+
});
|
|
143
149
|
};
|
|
144
150
|
}
|
|
145
151
|
//# sourceMappingURL=handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/lib/lifecycle/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS7D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAA+B,EAC/B,cAAoD;IAEpD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;IAE5C,OAAO,KAAK,UAAU,sBAAsB,CAC1C,GAAkC,EAClC,GAAmB;QAEnB,mEAAmE;
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/lib/lifecycle/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/lifecycle/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/lifecycle/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EAE1B,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,eAAO,MAAM,uBAAuB,+BAA+B,CAAC;AAcpE;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,yBAAyB,EAC/B,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACnD,kBAAkB,CAiGpB"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { insertStatusRow, queryStatusRows } from './status.js';
|
|
11
11
|
import { recordEvent as _recordEvent } from './events.js';
|
|
12
12
|
import { createLifecycleStepHandler } from './handler.js';
|
|
13
|
+
import { NotifySequenceNotFoundError } from '../errors.js';
|
|
13
14
|
/**
|
|
14
15
|
* Job type string registered with hazo_jobs for deferred lifecycle steps.
|
|
15
16
|
* Re-exported from `hazo_notify/lifecycle` for consumer use.
|
|
@@ -41,7 +42,7 @@ export function createLifecycleScheduler(opts, get_user_email) {
|
|
|
41
42
|
async function start({ user_id, sequence_id, }) {
|
|
42
43
|
const steps = opts.sequences[sequence_id];
|
|
43
44
|
if (!steps) {
|
|
44
|
-
throw new
|
|
45
|
+
throw new NotifySequenceNotFoundError(sequence_id);
|
|
45
46
|
}
|
|
46
47
|
const now = new Date();
|
|
47
48
|
for (const step of steps) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/lib/lifecycle/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/lib/lifecycle/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG,oCAA8B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/lib/preferences/storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/lib/preferences/storage.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,cAAc,EACf,MAAM,YAAY,CAAC;AAqBpB;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAS3B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAuCtF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU9F;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAKpF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAuCnD"}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Uses rawQuery via getInboxConnection() — compatible with both SQLite and Postgres.
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { generateRequestId } from 'hazo_core';
|
|
8
8
|
import { getInboxConnection } from '../inbox/connection.js';
|
|
9
|
+
import { HazoValidationError } from 'hazo_core';
|
|
9
10
|
const TABLE = 'hazo_notify_preferences';
|
|
10
11
|
function conn() {
|
|
11
12
|
return getInboxConnection();
|
|
@@ -38,6 +39,10 @@ export async function list_preferences(opts) {
|
|
|
38
39
|
* Upsert a preference row. A second call with the same key updates `mode`.
|
|
39
40
|
*/
|
|
40
41
|
export async function set_preference(input) {
|
|
42
|
+
const VALID_MODES = ['on', 'off', 'digest'];
|
|
43
|
+
if (!VALID_MODES.includes(input.mode)) {
|
|
44
|
+
throw new HazoValidationError({ code: 'HAZO_NOTIFY_INVALID_PREF_MODE', pkg: 'hazo_notify', message: `Invalid preference mode '${input.mode}'. Must be one of: ${VALID_MODES.join(', ')}` });
|
|
45
|
+
}
|
|
41
46
|
const all = await list_preferences({ user_id: input.user_id });
|
|
42
47
|
const match = all.find(r => r.scope_id === (input.scope_id ?? null) &&
|
|
43
48
|
r.event_type === (input.event_type ?? null) &&
|
|
@@ -47,7 +52,7 @@ export async function set_preference(input) {
|
|
|
47
52
|
await conn().rawQuery(`UPDATE ${TABLE} SET mode = ?, updated_at = ? WHERE id = ?`, { params: [input.mode, now, match.id] });
|
|
48
53
|
return { ...match, mode: input.mode, updated_at: now };
|
|
49
54
|
}
|
|
50
|
-
const id =
|
|
55
|
+
const id = generateRequestId().slice(4);
|
|
51
56
|
const now = new Date().toISOString();
|
|
52
57
|
await conn().rawQuery(`INSERT INTO ${TABLE} (id, user_id, scope_id, event_type, channel_id, mode, created_at, updated_at)
|
|
53
58
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, { params: [id, input.user_id, input.scope_id ?? null, input.event_type ?? null, input.channel_id, input.mode, now, now] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/lib/preferences/storage.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC;;;;GAIG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/lib/preferences/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
/** Delivery mode for a channel. 'digest' is added in 5.3.0. */
|
|
19
|
-
export type PreferenceMode = 'on' | 'off';
|
|
19
|
+
export type PreferenceMode = 'on' | 'off' | 'digest';
|
|
20
20
|
/** A single preference row as returned by the storage layer. */
|
|
21
21
|
export interface PreferenceRow {
|
|
22
22
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/preferences/types.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/preferences/types.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,GAAG,QAAQ,CAAC;AAErD,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,wCAAwC;IACxC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,gCAAgC;AAChC,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,cAAc,CAAC;CAC/B"}
|
package/dist/lib/utils.d.ts
CHANGED
package/dist/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/lib/utils.js
CHANGED
package/dist/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- migrations/011_digest_status.pg.sql
|
|
2
|
+
-- Adds 'digesting' to the delivery status CHECK constraint and a partial index
|
|
3
|
+
-- for the digest flush job query.
|
|
4
|
+
-- CAUTION: Dropping + recreating a CHECK constraint requires a brief table
|
|
5
|
+
-- lock in Postgres. Run during low-traffic window.
|
|
6
|
+
|
|
7
|
+
BEGIN;
|
|
8
|
+
|
|
9
|
+
ALTER TABLE hazo_notify_channel_deliveries
|
|
10
|
+
DROP CONSTRAINT IF EXISTS hazo_notify_channel_deliveries_status_check;
|
|
11
|
+
|
|
12
|
+
ALTER TABLE hazo_notify_channel_deliveries
|
|
13
|
+
ADD CONSTRAINT hazo_notify_channel_deliveries_status_check
|
|
14
|
+
CHECK (status IN ('pending', 'sent', 'failed', 'digesting'));
|
|
15
|
+
|
|
16
|
+
-- Index for digest flush job: find all digesting rows per channel quickly.
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_delivery_digesting
|
|
18
|
+
ON hazo_notify_channel_deliveries (channel_id, created_at)
|
|
19
|
+
WHERE status = 'digesting';
|
|
20
|
+
|
|
21
|
+
COMMIT;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- migrations/011_digest_status.sqlite.sql
|
|
2
|
+
-- SQLite: recreate table with updated CHECK constraint.
|
|
3
|
+
-- Safe to run multiple times (DROP IF EXISTS + CREATE IF NOT EXISTS).
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS hazo_notify_channel_deliveries_new (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
inbox_id TEXT NOT NULL REFERENCES hazo_notify_inbox(id) ON DELETE CASCADE,
|
|
10
|
+
channel_id TEXT NOT NULL,
|
|
11
|
+
payload TEXT NOT NULL,
|
|
12
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
13
|
+
flush_after TEXT NOT NULL,
|
|
14
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
message_id TEXT,
|
|
16
|
+
last_error TEXT,
|
|
17
|
+
finalized_at TEXT,
|
|
18
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
19
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
20
|
+
CHECK (status IN ('pending', 'sent', 'failed', 'digesting'))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
INSERT INTO hazo_notify_channel_deliveries_new
|
|
24
|
+
SELECT * FROM hazo_notify_channel_deliveries;
|
|
25
|
+
|
|
26
|
+
DROP TABLE hazo_notify_channel_deliveries;
|
|
27
|
+
|
|
28
|
+
ALTER TABLE hazo_notify_channel_deliveries_new
|
|
29
|
+
RENAME TO hazo_notify_channel_deliveries;
|
|
30
|
+
|
|
31
|
+
-- Recreate indexes (were dropped with the original table)
|
|
32
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uniq_delivery_inbox_channel
|
|
33
|
+
ON hazo_notify_channel_deliveries (inbox_id, channel_id);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_delivery_due
|
|
36
|
+
ON hazo_notify_channel_deliveries (channel_id, flush_after)
|
|
37
|
+
WHERE status = 'pending';
|
|
38
|
+
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_delivery_digesting
|
|
40
|
+
ON hazo_notify_channel_deliveries (channel_id, created_at)
|
|
41
|
+
WHERE status = 'digesting';
|
|
42
|
+
|
|
43
|
+
COMMIT;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- migrations/012_preferences_digest.pg.sql
|
|
2
|
+
-- Extends hazo_notify_preferences.mode CHECK constraint to allow 'digest'.
|
|
3
|
+
BEGIN;
|
|
4
|
+
|
|
5
|
+
ALTER TABLE hazo_notify_preferences
|
|
6
|
+
DROP CONSTRAINT IF EXISTS hazo_notify_preferences_mode_check;
|
|
7
|
+
|
|
8
|
+
ALTER TABLE hazo_notify_preferences
|
|
9
|
+
ADD CONSTRAINT hazo_notify_preferences_mode_check
|
|
10
|
+
CHECK (mode IN ('on', 'off', 'digest'));
|
|
11
|
+
|
|
12
|
+
COMMIT;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- migrations/012_preferences_digest.sqlite.sql
|
|
2
|
+
-- Extends hazo_notify_preferences.mode CHECK constraint to allow 'digest'.
|
|
3
|
+
-- SQLite does not support ALTER TABLE ... ADD CONSTRAINT, so we recreate the table.
|
|
4
|
+
|
|
5
|
+
PRAGMA foreign_keys = OFF;
|
|
6
|
+
|
|
7
|
+
-- Create new table with updated CHECK constraint
|
|
8
|
+
CREATE TABLE hazo_notify_preferences_new (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
user_id TEXT NOT NULL,
|
|
11
|
+
scope_id TEXT, -- NULL = global
|
|
12
|
+
event_type TEXT, -- NULL = all events
|
|
13
|
+
channel_id TEXT NOT NULL,
|
|
14
|
+
mode TEXT NOT NULL DEFAULT 'on',
|
|
15
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
17
|
+
CHECK (mode IN ('on', 'off', 'digest'))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- Copy all data from old table
|
|
21
|
+
INSERT INTO hazo_notify_preferences_new
|
|
22
|
+
SELECT id, user_id, scope_id, event_type, channel_id, mode, created_at, updated_at
|
|
23
|
+
FROM hazo_notify_preferences;
|
|
24
|
+
|
|
25
|
+
-- Drop old table
|
|
26
|
+
DROP TABLE hazo_notify_preferences;
|
|
27
|
+
|
|
28
|
+
-- Rename new table to original name
|
|
29
|
+
ALTER TABLE hazo_notify_preferences_new RENAME TO hazo_notify_preferences;
|
|
30
|
+
|
|
31
|
+
-- Recreate indexes from migration 010
|
|
32
|
+
CREATE UNIQUE INDEX IF NOT EXISTS hazo_notify_pref_nn
|
|
33
|
+
ON hazo_notify_preferences (user_id, scope_id, event_type, channel_id)
|
|
34
|
+
WHERE scope_id IS NOT NULL AND event_type IS NOT NULL;
|
|
35
|
+
|
|
36
|
+
CREATE UNIQUE INDEX IF NOT EXISTS hazo_notify_pref_sn
|
|
37
|
+
ON hazo_notify_preferences (user_id, event_type, channel_id)
|
|
38
|
+
WHERE scope_id IS NULL AND event_type IS NOT NULL;
|
|
39
|
+
|
|
40
|
+
CREATE UNIQUE INDEX IF NOT EXISTS hazo_notify_pref_en
|
|
41
|
+
ON hazo_notify_preferences (user_id, scope_id, channel_id)
|
|
42
|
+
WHERE scope_id IS NOT NULL AND event_type IS NULL;
|
|
43
|
+
|
|
44
|
+
CREATE UNIQUE INDEX IF NOT EXISTS hazo_notify_pref_ss
|
|
45
|
+
ON hazo_notify_preferences (user_id, channel_id)
|
|
46
|
+
WHERE scope_id IS NULL AND event_type IS NULL;
|
|
47
|
+
|
|
48
|
+
CREATE INDEX IF NOT EXISTS hazo_notify_pref_user_idx
|
|
49
|
+
ON hazo_notify_preferences (user_id);
|
|
50
|
+
|
|
51
|
+
PRAGMA foreign_keys = ON;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_notify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Email notification system with scope-aware template management",
|
|
6
6
|
"main": "./dist/lib/index.js",
|
|
@@ -124,21 +124,32 @@
|
|
|
124
124
|
"dependencies": {
|
|
125
125
|
"clsx": "^2.1.1",
|
|
126
126
|
"handlebars": "^4.7.8",
|
|
127
|
-
"hazo_config": "^2.1.0",
|
|
128
127
|
"isomorphic-dompurify": "^2.9.0",
|
|
129
128
|
"lru-cache": "^11.3.6",
|
|
130
129
|
"tailwind-merge": "^3.5.0"
|
|
131
130
|
},
|
|
132
131
|
"peerDependencies": {
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
132
|
+
"hazo_api": "^2.0.0",
|
|
133
|
+
"hazo_config": "^2.1.5",
|
|
134
|
+
"hazo_connect": "^3.0.0",
|
|
135
|
+
"hazo_core": "^1.0.0",
|
|
136
|
+
"hazo_jobs": "^0.12.0",
|
|
137
|
+
"hazo_logs": "^2.0.0",
|
|
138
|
+
"hazo_ui": "^3.1.0"
|
|
137
139
|
},
|
|
138
140
|
"peerDependenciesMeta": {
|
|
141
|
+
"hazo_api": {
|
|
142
|
+
"optional": true
|
|
143
|
+
},
|
|
144
|
+
"hazo_config": {
|
|
145
|
+
"optional": true
|
|
146
|
+
},
|
|
139
147
|
"hazo_connect": {
|
|
140
148
|
"optional": true
|
|
141
149
|
},
|
|
150
|
+
"hazo_core": {
|
|
151
|
+
"optional": true
|
|
152
|
+
},
|
|
142
153
|
"hazo_logs": {
|
|
143
154
|
"optional": true
|
|
144
155
|
},
|
|
@@ -201,9 +212,9 @@
|
|
|
201
212
|
"class-variance-authority": "^0.7.0",
|
|
202
213
|
"clsx": "^2.1.1",
|
|
203
214
|
"cmdk": "^1.1.1",
|
|
204
|
-
"hazo_auth": "^
|
|
205
|
-
"hazo_connect": "^
|
|
206
|
-
"hazo_ui": "^
|
|
215
|
+
"hazo_auth": "^9.0.0",
|
|
216
|
+
"hazo_connect": "^3.0.0",
|
|
217
|
+
"hazo_ui": "^3.1.0",
|
|
207
218
|
"jest": "^30.2.0",
|
|
208
219
|
"jest-environment-jsdom": "^30.4.1",
|
|
209
220
|
"lexical": "^0.39.0",
|