neopg 0.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.
@@ -0,0 +1,173 @@
1
+ const originCache = new Map()
2
+ , originStackCache = new Map()
3
+ , originError = Symbol('OriginError')
4
+
5
+ const CLOSE = module.exports.CLOSE = {}
6
+ const Query = module.exports.Query = class Query extends Promise {
7
+ constructor(strings, args, handler, canceller, options = {}) {
8
+ let resolve
9
+ , reject
10
+
11
+ super((a, b) => {
12
+ resolve = a
13
+ reject = b
14
+ })
15
+
16
+ this.tagged = Array.isArray(strings.raw)
17
+ this.strings = strings
18
+ this.args = args
19
+ this.handler = handler
20
+ this.canceller = canceller
21
+ this.options = options
22
+
23
+ this.state = null
24
+ this.statement = null
25
+
26
+ this.resolve = x => (this.active = false, resolve(x))
27
+ this.reject = x => (this.active = false, reject(x))
28
+
29
+ this.active = false
30
+ this.cancelled = null
31
+ this.executed = false
32
+ this.signature = ''
33
+
34
+ this[originError] = this.handler.debug
35
+ ? new Error()
36
+ : this.tagged && cachedError(this.strings)
37
+ }
38
+
39
+ get origin() {
40
+ return (this.handler.debug
41
+ ? this[originError].stack
42
+ : this.tagged && originStackCache.has(this.strings)
43
+ ? originStackCache.get(this.strings)
44
+ : originStackCache.set(this.strings, this[originError].stack).get(this.strings)
45
+ ) || ''
46
+ }
47
+
48
+ static get [Symbol.species]() {
49
+ return Promise
50
+ }
51
+
52
+ cancel() {
53
+ return this.canceller && (this.canceller(this), this.canceller = null)
54
+ }
55
+
56
+ simple() {
57
+ this.options.simple = true
58
+ this.options.prepare = false
59
+ return this
60
+ }
61
+
62
+ async readable() {
63
+ this.simple()
64
+ this.streaming = true
65
+ return this
66
+ }
67
+
68
+ async writable() {
69
+ this.simple()
70
+ this.streaming = true
71
+ return this
72
+ }
73
+
74
+ cursor(rows = 1, fn) {
75
+ this.options.simple = false
76
+ if (typeof rows === 'function') {
77
+ fn = rows
78
+ rows = 1
79
+ }
80
+
81
+ this.cursorRows = rows
82
+
83
+ if (typeof fn === 'function')
84
+ return (this.cursorFn = fn, this)
85
+
86
+ let prev
87
+ return {
88
+ [Symbol.asyncIterator]: () => ({
89
+ next: () => {
90
+ if (this.executed && !this.active)
91
+ return { done: true }
92
+
93
+ prev && prev()
94
+ const promise = new Promise((resolve, reject) => {
95
+ this.cursorFn = value => {
96
+ resolve({ value, done: false })
97
+ return new Promise(r => prev = r)
98
+ }
99
+ this.resolve = () => (this.active = false, resolve({ done: true }))
100
+ this.reject = x => (this.active = false, reject(x))
101
+ })
102
+ this.execute()
103
+ return promise
104
+ },
105
+ return() {
106
+ prev && prev(CLOSE)
107
+ return { done: true }
108
+ }
109
+ })
110
+ }
111
+ }
112
+
113
+ describe() {
114
+ this.options.simple = false
115
+ this.onlyDescribe = this.options.prepare = true
116
+ return this
117
+ }
118
+
119
+ stream() {
120
+ throw new Error('.stream has been renamed to .forEach')
121
+ }
122
+
123
+ forEach(fn) {
124
+ this.forEachFn = fn
125
+ this.handle()
126
+ return this
127
+ }
128
+
129
+ raw() {
130
+ this.isRaw = true
131
+ return this
132
+ }
133
+
134
+ values() {
135
+ this.isRaw = 'values'
136
+ return this
137
+ }
138
+
139
+ async handle() {
140
+ !this.executed && (this.executed = true) && await 1 && this.handler(this)
141
+ }
142
+
143
+ execute() {
144
+ this.handle()
145
+ return this
146
+ }
147
+
148
+ then() {
149
+ this.handle()
150
+ return super.then.apply(this, arguments)
151
+ }
152
+
153
+ catch() {
154
+ this.handle()
155
+ return super.catch.apply(this, arguments)
156
+ }
157
+
158
+ finally() {
159
+ this.handle()
160
+ return super.finally.apply(this, arguments)
161
+ }
162
+ }
163
+
164
+ function cachedError(xs) {
165
+ if (originCache.has(xs))
166
+ return originCache.get(xs)
167
+
168
+ const x = Error.stackTraceLimit
169
+ Error.stackTraceLimit = 4
170
+ originCache.set(xs, new Error())
171
+ Error.stackTraceLimit = x
172
+ return originCache.get(xs)
173
+ }
@@ -0,0 +1,31 @@
1
+ module.exports = Queue
2
+
3
+ function Queue(initial = []) {
4
+ let xs = initial.slice()
5
+ let index = 0
6
+
7
+ return {
8
+ get length() {
9
+ return xs.length - index
10
+ },
11
+ remove: (x) => {
12
+ const index = xs.indexOf(x)
13
+ return index === -1
14
+ ? null
15
+ : (xs.splice(index, 1), x)
16
+ },
17
+ push: (x) => (xs.push(x), x),
18
+ shift: () => {
19
+ const out = xs[index++]
20
+
21
+ if (index === xs.length) {
22
+ index = 0
23
+ xs = []
24
+ } else {
25
+ xs[index - 1] = undefined
26
+ }
27
+
28
+ return out
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,16 @@
1
+ module.exports = class Result extends Array {
2
+ constructor() {
3
+ super()
4
+ Object.defineProperties(this, {
5
+ count: { value: null, writable: true },
6
+ state: { value: null, writable: true },
7
+ command: { value: null, writable: true },
8
+ columns: { value: null, writable: true },
9
+ statement: { value: null, writable: true }
10
+ })
11
+ }
12
+
13
+ static get [Symbol.species]() {
14
+ return Array
15
+ }
16
+ }
@@ -0,0 +1,277 @@
1
+ const noop = () => { /* noop */ }
2
+
3
+ module.exports = Subscribe;function Subscribe(postgres, options) {
4
+ const subscribers = new Map()
5
+ , slot = 'postgresjs_' + Math.random().toString(36).slice(2)
6
+ , state = {}
7
+
8
+ let connection
9
+ , stream
10
+ , ended = false
11
+
12
+ const sql = subscribe.sql = postgres({
13
+ ...options,
14
+ transform: { column: {}, value: {}, row: {} },
15
+ max: 1,
16
+ fetch_types: false,
17
+ idle_timeout: null,
18
+ max_lifetime: null,
19
+ connection: {
20
+ ...options.connection,
21
+ replication: 'database'
22
+ },
23
+ onclose: async function() {
24
+ if (ended)
25
+ return
26
+ stream = null
27
+ state.pid = state.secret = undefined
28
+ connected(await init(sql, slot, options.publications))
29
+ subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
30
+ },
31
+ no_subscribe: true
32
+ })
33
+
34
+ const end = sql.end
35
+ , close = sql.close
36
+
37
+ sql.end = async() => {
38
+ ended = true
39
+ stream && (await new Promise(r => (stream.once('close', r), stream.end())))
40
+ return end()
41
+ }
42
+
43
+ sql.close = async() => {
44
+ stream && (await new Promise(r => (stream.once('close', r), stream.end())))
45
+ return close()
46
+ }
47
+
48
+ return subscribe
49
+
50
+ async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
51
+ event = parseEvent(event)
52
+
53
+ if (!connection)
54
+ connection = init(sql, slot, options.publications)
55
+
56
+ const subscriber = { fn, onsubscribe }
57
+ const fns = subscribers.has(event)
58
+ ? subscribers.get(event).add(subscriber)
59
+ : subscribers.set(event, new Set([subscriber])).get(event)
60
+
61
+ const unsubscribe = () => {
62
+ fns.delete(subscriber)
63
+ fns.size === 0 && subscribers.delete(event)
64
+ }
65
+
66
+ return connection.then(x => {
67
+ connected(x)
68
+ onsubscribe()
69
+ stream && stream.on('error', onerror)
70
+ return { unsubscribe, state, sql }
71
+ })
72
+ }
73
+
74
+ function connected(x) {
75
+ stream = x.stream
76
+ state.pid = x.state.pid
77
+ state.secret = x.state.secret
78
+ }
79
+
80
+ async function init(sql, slot, publications) {
81
+ if (!publications)
82
+ throw new Error('Missing publication names')
83
+
84
+ const xs = await sql.unsafe(
85
+ `CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
86
+ )
87
+
88
+ const [x] = xs
89
+
90
+ const stream = await sql.unsafe(
91
+ `START_REPLICATION SLOT ${ slot } LOGICAL ${
92
+ x.consistent_point
93
+ } (proto_version '1', publication_names '${ publications }')`
94
+ ).writable()
95
+
96
+ const state = {
97
+ lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
98
+ }
99
+
100
+ stream.on('data', data)
101
+ stream.on('error', error)
102
+ stream.on('close', sql.close)
103
+
104
+ return { stream, state: xs.state }
105
+
106
+ function error(e) {
107
+ console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
108
+ }
109
+
110
+ function data(x) {
111
+ if (x[0] === 0x77) {
112
+ parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
113
+ } else if (x[0] === 0x6b && x[17]) {
114
+ state.lsn = x.subarray(1, 9)
115
+ pong()
116
+ }
117
+ }
118
+
119
+ function handle(a, b) {
120
+ const path = b.relation.schema + '.' + b.relation.table
121
+ call('*', a, b)
122
+ call('*:' + path, a, b)
123
+ b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
124
+ call(b.command, a, b)
125
+ call(b.command + ':' + path, a, b)
126
+ b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
127
+ }
128
+
129
+ function pong() {
130
+ const x = Buffer.alloc(34)
131
+ x[0] = 'r'.charCodeAt(0)
132
+ x.fill(state.lsn, 1)
133
+ x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
134
+ stream.write(x)
135
+ }
136
+ }
137
+
138
+ function call(x, a, b) {
139
+ subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
140
+ }
141
+ }
142
+
143
+ function Time(x) {
144
+ return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
145
+ }
146
+
147
+ function parse(x, state, parsers, handle, transform) {
148
+ const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
149
+
150
+ Object.entries({
151
+ R: x => { // Relation
152
+ let i = 1
153
+ const r = state[x.readUInt32BE(i)] = {
154
+ schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
155
+ table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
156
+ columns: Array(x.readUInt16BE(i += 2)),
157
+ keys: []
158
+ }
159
+ i += 2
160
+
161
+ let columnIndex = 0
162
+ , column
163
+
164
+ while (i < x.length) {
165
+ column = r.columns[columnIndex++] = {
166
+ key: x[i++],
167
+ name: transform.column.from
168
+ ? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
169
+ : x.toString('utf8', i, i = x.indexOf(0, i)),
170
+ type: x.readUInt32BE(i += 1),
171
+ parser: parsers[x.readUInt32BE(i)],
172
+ atttypmod: x.readUInt32BE(i += 4)
173
+ }
174
+
175
+ column.key && r.keys.push(column)
176
+ i += 4
177
+ }
178
+ },
179
+ Y: () => { /* noop */ }, // Type
180
+ O: () => { /* noop */ }, // Origin
181
+ B: x => { // Begin
182
+ state.date = Time(x.readBigInt64BE(9))
183
+ state.lsn = x.subarray(1, 9)
184
+ },
185
+ I: x => { // Insert
186
+ let i = 1
187
+ const relation = state[x.readUInt32BE(i)]
188
+ const { row } = tuples(x, relation.columns, i += 7, transform)
189
+
190
+ handle(row, {
191
+ command: 'insert',
192
+ relation
193
+ })
194
+ },
195
+ D: x => { // Delete
196
+ let i = 1
197
+ const relation = state[x.readUInt32BE(i)]
198
+ i += 4
199
+ const key = x[i] === 75
200
+ handle(key || x[i] === 79
201
+ ? tuples(x, relation.columns, i += 3, transform).row
202
+ : null
203
+ , {
204
+ command: 'delete',
205
+ relation,
206
+ key
207
+ })
208
+ },
209
+ U: x => { // Update
210
+ let i = 1
211
+ const relation = state[x.readUInt32BE(i)]
212
+ i += 4
213
+ const key = x[i] === 75
214
+ const xs = key || x[i] === 79
215
+ ? tuples(x, relation.columns, i += 3, transform)
216
+ : null
217
+
218
+ xs && (i = xs.i)
219
+
220
+ const { row } = tuples(x, relation.columns, i + 3, transform)
221
+
222
+ handle(row, {
223
+ command: 'update',
224
+ relation,
225
+ key,
226
+ old: xs && xs.row
227
+ })
228
+ },
229
+ T: () => { /* noop */ }, // Truncate,
230
+ C: () => { /* noop */ } // Commit
231
+ }).reduce(char, {})[x[0]](x)
232
+ }
233
+
234
+ function tuples(x, columns, xi, transform) {
235
+ let type
236
+ , column
237
+ , value
238
+
239
+ const row = transform.raw ? new Array(columns.length) : {}
240
+ for (let i = 0; i < columns.length; i++) {
241
+ type = x[xi++]
242
+ column = columns[i]
243
+ value = type === 110 // n
244
+ ? null
245
+ : type === 117 // u
246
+ ? undefined
247
+ : column.parser === undefined
248
+ ? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
249
+ : column.parser.array === true
250
+ ? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
251
+ : column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
252
+
253
+ transform.raw
254
+ ? (row[i] = transform.raw === true
255
+ ? value
256
+ : transform.value.from ? transform.value.from(value, column) : value)
257
+ : (row[column.name] = transform.value.from
258
+ ? transform.value.from(value, column)
259
+ : value
260
+ )
261
+ }
262
+
263
+ return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
264
+ }
265
+
266
+ function parseEvent(x) {
267
+ const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
268
+
269
+ if (!xs)
270
+ throw new Error('Malformed subscribe pattern: ' + x)
271
+
272
+ const [, command, path, key] = xs
273
+
274
+ return (command || '*')
275
+ + (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
276
+ + (key ? '=' + key : '')
277
+ }