kempo 4.2.40 → 4.3.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 (33) hide show
  1. package/README.md +7 -0
  2. package/app-public/default.template.html +1 -1
  3. package/dist/admin/nav.fragment.html +5 -0
  4. package/dist/admin/realtime/index.page.html +81 -0
  5. package/dist/kempo/api/realtime/WS.js +2 -0
  6. package/dist/kempo/api/realtime/WS.js.map +1 -0
  7. package/dist/kempo/api/realtime/connections/GET.js +2 -0
  8. package/dist/kempo/api/realtime/connections/GET.js.map +1 -0
  9. package/dist/kempo/icons/credit_card.svg +1 -0
  10. package/dist/kempo/icons/sensors.svg +1 -0
  11. package/dist/kempo/realtime.js +2 -0
  12. package/dist/kempo/realtime.js.map +1 -0
  13. package/dist/kempo/sdk.js +1 -1
  14. package/dist/kempo/sdk.js.map +1 -1
  15. package/middleware/kempo.js +30 -15
  16. package/package.json +2 -2
  17. package/scripts/init-db.js +4 -1
  18. package/server/db/schema.js +30 -1
  19. package/server/sdk.js +5 -0
  20. package/server/utils/extensions/contentDirs.js +27 -0
  21. package/server/utils/realtime/Hub.js +676 -0
  22. package/server/utils/realtime/channels.js +247 -0
  23. package/server/utils/realtime/closeConnection.js +4 -0
  24. package/server/utils/realtime/constants.js +20 -0
  25. package/server/utils/realtime/duration.js +18 -0
  26. package/server/utils/realtime/getHub.js +15 -0
  27. package/server/utils/realtime/index.js +14 -0
  28. package/server/utils/realtime/listConnections.js +4 -0
  29. package/server/utils/realtime/listSubscribers.js +4 -0
  30. package/server/utils/realtime/pruneMessages.js +45 -0
  31. package/server/utils/realtime/publish.js +86 -0
  32. package/server/utils/realtime/sendToConnection.js +4 -0
  33. package/server/utils/routing/serveUnmatched.js +8 -1
@@ -1,4 +1,4 @@
1
- import { pgTable, text, timestamp, boolean, index, jsonb } from 'drizzle-orm/pg-core';
1
+ import { pgTable, text, timestamp, boolean, index, jsonb, bigserial, bigint } from 'drizzle-orm/pg-core';
2
2
 
3
3
  /*
4
4
  Auth Tables
@@ -114,6 +114,35 @@ export const extension = pgTable('extension', {
114
114
  updatedAt: timestamp('updatedAt').notNull(),
115
115
  });
116
116
 
117
+ /*
118
+ Realtime System
119
+ */
120
+
121
+ /*
122
+ A published message, kept only for channels that opt in to persistence. `id` is what a client
123
+ hands back as `since` to replay what it missed, so it must be ordered within a channel: publish
124
+ takes a per-channel lock, otherwise two rows could commit out of id order and a replay from the
125
+ earlier id would skip the later-committing one for good.
126
+ */
127
+ export const realtimeMessage = pgTable('realtimeMessage', {
128
+ id: bigserial('id', { mode: 'number' }).primaryKey(),
129
+ channel: text('channel').notNull(),
130
+ data: jsonb('data').notNull(),
131
+ createdAt: timestamp('createdAt').notNull(),
132
+ }, table => [
133
+ index('realtimeMessage_channel_id_idx').on(table.channel, table.id),
134
+ index('realtimeMessage_createdAt_idx').on(table.createdAt),
135
+ ]);
136
+
137
+ /*
138
+ How far a channel has been pruned. Ids are shared across channels, so a gap cannot be inferred from
139
+ id arithmetic; a client whose `since` is below this watermark has provably missed messages.
140
+ */
141
+ export const realtimeChannel = pgTable('realtimeChannel', {
142
+ channel: text('channel').primaryKey(),
143
+ prunedThrough: bigint('prunedThrough', { mode: 'number' }).notNull().default(0),
144
+ });
145
+
117
146
  /*
118
147
  Triggers to prevent deletion of system resources
119
148
  */
package/server/sdk.js CHANGED
@@ -196,3 +196,8 @@ export { default as listExtensions } from './utils/extensions/listExtensions.js'
196
196
  export { default as uninstallExtension } from './utils/extensions/uninstallExtension.js';
197
197
  export { default as updateExtension } from './utils/extensions/updateExtension.js';
198
198
 
199
+ /*
200
+ Realtime
201
+ */
202
+
203
+ export * as realtime from './utils/realtime/index.js';
@@ -0,0 +1,27 @@
1
+ import { join } from 'path';
2
+ import { getEnabledExtensions } from './scopeCache.js';
3
+
4
+ const NODE_MODULES = join(process.cwd(), 'node_modules');
5
+
6
+ /*
7
+ The directories an enabled extension may contribute *.global.html and *.fragment.html from, split
8
+ by which half of the site is rendering: `admin/` for the admin portal, `public/` for the live site.
9
+
10
+ Each is read straight from the package at render time, the same way an extension's pages and
11
+ routes are resolved, so nothing is copied anywhere on install: enabling, disabling, upgrading or
12
+ removing an extension takes effect immediately, and there is nothing left behind to clean up.
13
+
14
+ That is also the whole of the on/off switch for this content — a disabled extension drops out of
15
+ these lists and its files are simply never read. There is no `enabled` flag on a packaged file the
16
+ way there is on admin-authored global content, because there is no stored row to carry one.
17
+
18
+ Lives here rather than in middleware/kempo.js so that server utils can reach it too — the 404 page
19
+ is rendered from server/utils/routing/serveUnmatched.js, and importing the middleware from there
20
+ would be circular.
21
+ */
22
+ const extensionDirs = async sub =>
23
+ (await getEnabledExtensions()).map(ext => join(NODE_MODULES, ext.name, sub));
24
+
25
+ export const extensionAdminDirs = () => extensionDirs('admin');
26
+
27
+ export const extensionPublicDirs = () => extensionDirs('public');