@everystack/events 0.3.0 → 0.4.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "PostgreSQL LISTEN/NOTIFY bridge for serverless — WebSocket fan-out + Lambda invocation",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@everystack/model": "0.
|
|
58
|
+
"@everystack/model": "0.4.4"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@aws-sdk/client-apigatewaymanagementapi": "3.1053.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@aws-sdk/signature-v4a": "3.1031.0",
|
|
65
65
|
"@tanstack/react-query": "5.100.9",
|
|
66
66
|
"drizzle-orm": "0.41.0",
|
|
67
|
-
"react": "19.2.
|
|
67
|
+
"react": "19.2.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependenciesMeta": {
|
|
70
70
|
"@aws-sdk/client-cloudfront-keyvaluestore": {
|
|
@@ -96,10 +96,10 @@
|
|
|
96
96
|
"@types/react": "19.2.14",
|
|
97
97
|
"drizzle-orm": "0.41.0",
|
|
98
98
|
"jest": "29.7.0",
|
|
99
|
-
"react": "19.2.
|
|
99
|
+
"react": "19.2.0",
|
|
100
100
|
"ts-jest": "29.4.9",
|
|
101
101
|
"typescript": "5.9.3",
|
|
102
|
-
"@everystack/auth": "0.
|
|
102
|
+
"@everystack/auth": "0.4.0"
|
|
103
103
|
},
|
|
104
104
|
"scripts": {
|
|
105
105
|
"test": "jest",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The events package's DECLARED derived layer — the notify functions and the trigger
|
|
3
|
+
* factory, as descriptors (docs/plans/read-model-everywhere.md). Supersedes the raw-SQL
|
|
4
|
+
* generators in `../triggers` (kept, deprecated, for the transition): declared objects
|
|
5
|
+
* are content-hashed, dependency-ordered, drift-checked by db:reconcile, and — because
|
|
6
|
+
* every compiled function revokes EXECUTE from PUBLIC — secure by default.
|
|
7
|
+
*
|
|
8
|
+
* Trigger attachment stays the APP's choice, now with owner consent visible in the
|
|
9
|
+
* model: each emitting table declares `triggers: [eventsTrigger('posts_events')]`
|
|
10
|
+
* itself — a package can offer a trigger, never silently attach one.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { defineFunction, trigger, sql, type TriggerSpec } from '@everystack/model';
|
|
14
|
+
|
|
15
|
+
/** `everystack_notify()` — row-change events onto the `events` NOTIFY channel. */
|
|
16
|
+
export const notifyFunction = defineFunction('everystack_notify', {
|
|
17
|
+
returns: 'trigger',
|
|
18
|
+
language: 'plpgsql',
|
|
19
|
+
body: sql`
|
|
20
|
+
DECLARE
|
|
21
|
+
row_id text;
|
|
22
|
+
BEGIN
|
|
23
|
+
-- A composite-key table (e.g. likes (user_id, post_id)) has no id column, so NEW.id
|
|
24
|
+
-- raises undefined_column — emit a NULL id rather than fail the whole write.
|
|
25
|
+
BEGIN
|
|
26
|
+
row_id := COALESCE(NEW.id, OLD.id);
|
|
27
|
+
EXCEPTION WHEN undefined_column THEN
|
|
28
|
+
row_id := NULL;
|
|
29
|
+
END;
|
|
30
|
+
PERFORM pg_notify(
|
|
31
|
+
'events',
|
|
32
|
+
json_build_object(
|
|
33
|
+
'schema', TG_TABLE_SCHEMA,
|
|
34
|
+
'table', TG_TABLE_NAME,
|
|
35
|
+
'op', lower(TG_OP),
|
|
36
|
+
'id', row_id
|
|
37
|
+
)::text
|
|
38
|
+
);
|
|
39
|
+
RETURN COALESCE(NEW, OLD);
|
|
40
|
+
END`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The event trigger for one emitting table — declared ON the model
|
|
45
|
+
* (`triggers: [eventsTrigger('posts_events')]`). Convention: `<table>_events`,
|
|
46
|
+
* matching what `generateTriggerSQL` created, so an existing database adopts via
|
|
47
|
+
* `db:reconcile --baseline` instead of rebuilding.
|
|
48
|
+
*/
|
|
49
|
+
export function eventsTrigger(name: string): TriggerSpec {
|
|
50
|
+
return trigger(name, {
|
|
51
|
+
timing: 'after',
|
|
52
|
+
events: ['insert', 'update', 'delete'],
|
|
53
|
+
forEach: 'row',
|
|
54
|
+
execute: notifyFunction,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `everystack_event_actions_notify()` — the rules table's self-notify (reload signal). */
|
|
59
|
+
export const eventActionsNotifyFunction = defineFunction('everystack_event_actions_notify', {
|
|
60
|
+
returns: 'trigger',
|
|
61
|
+
language: 'plpgsql',
|
|
62
|
+
body: sql`
|
|
63
|
+
BEGIN
|
|
64
|
+
PERFORM pg_notify('_event_actions', '{}');
|
|
65
|
+
RETURN COALESCE(NEW, OLD);
|
|
66
|
+
END`,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** The self-notify trigger for ops.event_actions — declared on the EventAction model. */
|
|
70
|
+
export function eventActionsTrigger(): TriggerSpec {
|
|
71
|
+
return trigger('event_actions_notify', {
|
|
72
|
+
timing: 'after',
|
|
73
|
+
events: ['insert', 'update', 'delete'],
|
|
74
|
+
forEach: 'row',
|
|
75
|
+
execute: eventActionsNotifyFunction,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { defineModel, field, can, index } from '@everystack/model';
|
|
20
|
+
import { eventActionsTrigger } from './descriptors';
|
|
20
21
|
|
|
21
22
|
export const EventAction = defineModel('event_actions', {
|
|
22
23
|
schema: 'ops',
|
|
23
24
|
writtenBy: 'worker',
|
|
24
25
|
private: true,
|
|
26
|
+
// Owner consent, visible in the model: the reload-signal trigger is declared HERE.
|
|
27
|
+
triggers: [eventActionsTrigger()],
|
|
25
28
|
fields: {
|
|
26
29
|
id: field.uuid().primaryKey().defaultRandom(),
|
|
27
30
|
tenantId: field.uuid().nullable(), // NULL = platform-wide, non-NULL = tenant-scoped
|
package/src/models/index.ts
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { defineModule } from '@everystack/model';
|
|
11
11
|
import { EventAction } from './event-action';
|
|
12
|
-
import {
|
|
12
|
+
import { eventActionsNotifyFunction, notifyFunction } from './descriptors';
|
|
13
13
|
|
|
14
14
|
export { EventAction } from './event-action';
|
|
15
|
+
export { notifyFunction, eventsTrigger, eventActionsNotifyFunction, eventActionsTrigger } from './descriptors';
|
|
15
16
|
|
|
16
17
|
/** All events-owned models, for the app to spread into its db:generate model list. */
|
|
17
18
|
export const eventModels = [EventAction];
|
|
@@ -22,5 +23,7 @@ export const eventModels = [EventAction];
|
|
|
22
23
|
*/
|
|
23
24
|
export const eventModule = defineModule({
|
|
24
25
|
models: eventModels,
|
|
25
|
-
functions
|
|
26
|
+
// The notify functions are DECLARED now (content-hashed, drift-checked, PUBLIC-revoked);
|
|
27
|
+
// notifyFunction ships here so app models can reference it via eventsTrigger(...).
|
|
28
|
+
derived: [notifyFunction, eventActionsNotifyFunction],
|
|
26
29
|
});
|