@jcbuisson/express-x-plugins 4.0.13 → 4.1.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": "@jcbuisson/express-x-plugins",
3
- "version": "4.0.13",
3
+ "version": "4.1.0",
4
4
  "description": "Plugins for express-x",
5
5
  "type": "module",
6
6
  "main": "./src/electric-server-plugin.mjs",
@@ -1,16 +1,17 @@
1
1
  import { Shape, ShapeStream } from '@electric-sql/client'
2
- import { firstValueFrom, Observable, Subject, takeUntil } from 'rxjs'
3
- import { getCurrentScope, onScopeDispose, ref } from 'vue'
2
+ import { Observable } from 'rxjs'
3
+ // import { firstValueFrom, Subject, takeUntil } from 'rxjs'
4
+ // import { getCurrentScope, onScopeDispose } from 'vue'
4
5
 
5
6
  /**
6
7
  * Add Electric-backed reactive models to an Express-X client.
7
8
  *
8
9
  * Usage:
9
10
  * electricClientPlugin(app)
10
- * const todo = app.createElectricModel('todos') // primary key is provided by client
11
- * const todo = app.createElectricModel('todos', { idGeneration: 'server' }) // primary key is server-generated
12
- * todo.getObservable({ completed: false }).subscribe(...)
13
- * const completedTodos = await todo.findMany({ completed: false })
11
+ * const todoModel = app.createElectricModel('todos') // primary key is automatically provided by server (auto-increment)
12
+ * const todoMilk = await todoModel.create({ label: "Buy milk" })
13
+ * todoMilk.update({ completed: true })
14
+ * todoModel.getObservable({ completed: false }).subscribe(...) // subscribe to an observable for todos with completed===false
14
15
  */
15
16
  export function electricClientPlugin(app, options = {}) {
16
17
  const shapePath = options.shapePath ?? '/electric/v1/shape'
@@ -23,10 +24,10 @@ export function electricClientPlugin(app, options = {}) {
23
24
  const service = app.service(modelName)
24
25
  const url = modelOptions.url ?? modelPath(shapePath, modelName)
25
26
  const streamOptions = modelOptions.streamOptions ?? {}
26
- const idGeneration = modelOptions.idGeneration ?? 'client'
27
- if (!['client', 'server'].includes(idGeneration)) {
28
- throw new TypeError("idGeneration must be 'client' or 'server'")
29
- }
27
+ // const idGeneration = modelOptions.idGeneration ?? 'client'
28
+ // if (!['client', 'server'].includes(idGeneration)) {
29
+ // throw new TypeError("idGeneration must be 'client' or 'server'")
30
+ // }
30
31
 
31
32
  function getObservable(where = {}) {
32
33
  // Validate eagerly
@@ -52,24 +53,35 @@ export function electricClientPlugin(app, options = {}) {
52
53
  })
53
54
  }
54
55
 
55
- function findMany(where = {}) {
56
- const observable = getObservable(where)
57
- if (!getCurrentScope()) return firstValueFrom(observable)
58
-
59
- const scopeDisposed = new Subject()
60
- onScopeDispose(() => {
61
- scopeDisposed.next()
62
- scopeDisposed.complete()
63
- })
64
- return firstValueFrom(observable.pipe(takeUntil(scopeDisposed)))
65
- }
66
-
67
- async function create(data) {
68
- assertPlainObject(data, 'mutation data')
69
- if (idGeneration === 'server') return service.create(data)
70
- const uid = globalThis.crypto?.randomUUID?.()
71
- if (!uid) throw new Error('crypto.randomUUID() is required')
72
- return service.create(uid, data)
56
+ // function findMany(where = {}) {
57
+ // const observable = getObservable(where)
58
+ // if (!getCurrentScope()) return firstValueFrom(observable)
59
+
60
+ // const scopeDisposed = new Subject()
61
+ // onScopeDispose(() => {
62
+ // scopeDisposed.next()
63
+ // scopeDisposed.complete()
64
+ // })
65
+ // return firstValueFrom(observable.pipe(takeUntil(scopeDisposed)))
66
+ // }
67
+
68
+ // async function create(data) {
69
+ // assertPlainObject(data, 'mutation data')
70
+ // if (idGeneration === 'server') return service.create(data)
71
+ // const uid = globalThis.crypto?.randomUUID?.()
72
+ // if (!uid) throw new Error('crypto.randomUUID() is required')
73
+ // return service.create(uid, data)
74
+ // }
75
+
76
+ async function create(uidOrData, data) {
77
+ const hasUid = data !== undefined
78
+ const mutationData = hasUid ? data : uidOrData
79
+ assertPlainObject(mutationData, 'mutation data')
80
+ if (hasUid) {
81
+ return service.create(uidOrData, data)
82
+ } else {
83
+ return service.create(data)
84
+ }
73
85
  }
74
86
 
75
87
  async function update(id, data) {
@@ -80,7 +92,7 @@ export function electricClientPlugin(app, options = {}) {
80
92
  return service.delete(id)
81
93
  }
82
94
 
83
- return { getObservable, findMany, create, update, remove }
95
+ return { getObservable, /*findMany,*/ create, update, remove }
84
96
  }
85
97
 
86
98
  return Object.assign(app, { createElectricModel })
@@ -16,9 +16,11 @@ export function electricOfflinePlugin(app, db, models, options = {}) {
16
16
  const shapePath = options.shapePath ?? '/electric/v1/shape/:model'
17
17
  const fetchImpl = options.fetch ?? globalThis.fetch
18
18
  if (typeof fetchImpl !== 'function') throw new TypeError('a fetch implementation is required')
19
+ const authorizeFunc = options.authorize || (() => true)
20
+ if (typeof authorizeFunc !== 'function') throw new TypeError('an authorize function is required')
19
21
 
20
22
  async function authorize(context, modelName, action, args) {
21
- const allowed = await options.authorize(context, { modelName, action, args })
23
+ const allowed = await authorizeFunc(context, { modelName, action, args })
22
24
  if (!allowed) {
23
25
  const error = new Error(`not authorized to ${action} '${modelName}'`)
24
26
  error.code = 'forbidden'
@@ -54,13 +56,13 @@ export function electricOfflinePlugin(app, db, models, options = {}) {
54
56
 
55
57
  // create(data): the primary key is server-generated
56
58
  // create(uid, data): uid (the primary key) is provided by the client
57
- create: async function(idOrData, data) {
59
+ create: async function(uidOrData, data) {
58
60
  const hasClientId = data !== undefined
59
- const mutationData = hasClientId ? data : idOrData
60
- await authorize(this, model.name, 'create', hasClientId ? [idOrData, mutationData] : [mutationData])
61
+ const mutationData = hasClientId ? data : uidOrData
62
+ await authorize(this, model.name, 'create', hasClientId ? [uidOrData, mutationData] : [mutationData])
61
63
  assertPlainObject(mutationData, 'mutation data')
62
64
  const safeData = hasClientId
63
- ? { ...mutationData, [model.primaryKey]: idOrData }
65
+ ? { ...mutationData, [model.primaryKey]: uidOrData }
64
66
  : { ...mutationData }
65
67
  const entries = Object.entries(safeData).filter(([, value]) => value !== undefined)
66
68
  const columns = entries.map(([column]) => quoteIdentifier(column, 'data column'))