@jcbuisson/express-x-plugins 5.0.0 → 5.0.1
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
CHANGED
|
@@ -56,12 +56,12 @@ npm install pg
|
|
|
56
56
|
```js
|
|
57
57
|
import { Pool } from 'pg'
|
|
58
58
|
import { expressX } from '@jcbuisson/express-x/server'
|
|
59
|
-
import {
|
|
59
|
+
import { electricServerPlugin, prepareElectricSyncSchema } from '@jcbuisson/express-x-plugins/electric-server'
|
|
60
60
|
|
|
61
61
|
const app = expressX()
|
|
62
62
|
const db = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
63
63
|
|
|
64
|
-
app.configure(
|
|
64
|
+
app.configure(electricServerPlugin, db, [
|
|
65
65
|
{ name: 'todos', tombstoneData: { title: '' } },
|
|
66
66
|
], {
|
|
67
67
|
sync: true,
|
|
@@ -100,7 +100,7 @@ rows. Configure `tombstoneData` for required columns without defaults, such as a
|
|
|
100
100
|
required title. Keep the cursor table and tombstones to protect delayed retries and
|
|
101
101
|
offline clients. This does not resolve concurrent edits by different clients.
|
|
102
102
|
|
|
103
|
-
Import `prepareElectricSyncSchema` alongside `
|
|
103
|
+
Import `prepareElectricSyncSchema` alongside `electricServerPlugin`. It adds
|
|
104
104
|
`version` and `deleted` columns and creates the shared version sequence and cursor
|
|
105
105
|
table. The primary key must be UUID in sync mode. All writes to synced tables must
|
|
106
106
|
advance `version`; hard deletes bypass tombstone confirmation.
|
package/package.json
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* @param {(string|{name:string,table?:string,primaryKey?:string})[]} models
|
|
7
7
|
* @param {object} options
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function electricServerPlugin(app, db, models, options = {}) {
|
|
10
10
|
if (!db || typeof db.query !== 'function') throw new TypeError('db must expose query(sql, values)')
|
|
11
11
|
// if (typeof options.authorize !== 'function') {
|
|
12
|
-
// throw new TypeError('
|
|
12
|
+
// throw new TypeError('electricServerPlugin requires an authorize(context, operation) policy')
|
|
13
13
|
// }
|
|
14
14
|
const configuredModels = normalizeModels(models)
|
|
15
15
|
const sync = options.sync === true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import test from 'node:test'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { electricServerPlugin, prepareElectricSyncSchema } from '../src/electric-server-plugin.mjs'
|
|
5
5
|
|
|
6
6
|
function fixture({ authorize = async () => true, fetch } = {}) {
|
|
7
7
|
const services = new Map()
|
|
@@ -17,7 +17,7 @@ function fixture({ authorize = async () => true, fetch } = {}) {
|
|
|
17
17
|
createService(name, methods) { services.set(name, methods) },
|
|
18
18
|
get(path, handler) { routes.set(path, handler) },
|
|
19
19
|
}
|
|
20
|
-
const registration =
|
|
20
|
+
const registration = electricServerPlugin(app, db, ['todos'], { authorize, fetch: fetch ?? globalThis.fetch })
|
|
21
21
|
return { app, db, services, routes, queries, registration }
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -65,7 +65,7 @@ test('protects the configured primary key during updates', async () => {
|
|
|
65
65
|
createService(name, methods) { services.set(name, methods) },
|
|
66
66
|
get() {},
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
electricServerPlugin(app, db, [{ name: 'people', primaryKey: 'id' }], {
|
|
69
69
|
authorize: async () => true,
|
|
70
70
|
})
|
|
71
71
|
|
|
@@ -115,7 +115,7 @@ test('shape proxy pins the configured table and keeps credentials server-side',
|
|
|
115
115
|
createService(name, methods) { services.set(name, methods) },
|
|
116
116
|
get(path, handler) { routes.set(path, handler) },
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
electricServerPlugin(app, db, [{ name: 'todo', table: 'todos' }], {
|
|
119
119
|
authorize: async () => true,
|
|
120
120
|
electricUrl: 'https://electric.example/v1/shape',
|
|
121
121
|
sourceId: 'source',
|
|
@@ -145,7 +145,7 @@ test('shape proxy pins the configured table and keeps credentials server-side',
|
|
|
145
145
|
|
|
146
146
|
test('rejects unsafe SQL identifiers', () => {
|
|
147
147
|
assert.throws(
|
|
148
|
-
() =>
|
|
148
|
+
() => electricServerPlugin(
|
|
149
149
|
{ createService() {}, get() {} },
|
|
150
150
|
{ query() {} },
|
|
151
151
|
['todos; DROP TABLE users'],
|
|
@@ -174,7 +174,7 @@ test('sync mode commits a versioned write and rejects replayed or stale revision
|
|
|
174
174
|
}
|
|
175
175
|
const db = { query: client.query.bind(client), async connect() { return client } }
|
|
176
176
|
const services = new Map()
|
|
177
|
-
|
|
177
|
+
electricServerPlugin({ createService(name, methods) { services.set(name, methods) }, get() {} }, db,
|
|
178
178
|
[{ name: 'todos', tombstoneData: { title: '' } }], { sync: true })
|
|
179
179
|
const service = services.get('todos')
|
|
180
180
|
const id = '64d76168-775f-481e-8974-18d31d835d9e'
|
|
@@ -212,7 +212,7 @@ test('sync delete writes a versioned tombstone and clears configured fields', as
|
|
|
212
212
|
}
|
|
213
213
|
const db = { query: client.query.bind(client), async connect() { return client } }
|
|
214
214
|
const services = new Map()
|
|
215
|
-
|
|
215
|
+
electricServerPlugin({ createService(name, methods) { services.set(name, methods) }, get() {} }, db,
|
|
216
216
|
[{ name: 'todos', tombstoneData: { title: '' } }], { sync: true })
|
|
217
217
|
const result = await services.get('todos').delete.call({}, id, {
|
|
218
218
|
clientId: '5e199e1c-23a4-473e-9989-14d4a1fb857e', revision: '1',
|