@electric-sql/client 0.2.2
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/LICENSE +177 -0
- package/README.md +80 -0
- package/dist/cjs/index.cjs +504 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.browser.mjs +2 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.legacy-esm.js +450 -0
- package/dist/index.legacy-esm.js.map +1 -0
- package/dist/index.mjs +478 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +69 -0
- package/src/client.ts +572 -0
- package/src/index.ts +2 -0
- package/src/parser.ts +130 -0
- package/src/types.ts +108 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ColumnInfo, Message, Schema, Value } from './types'
|
|
2
|
+
|
|
3
|
+
export type ParseFunction = (
|
|
4
|
+
value: string,
|
|
5
|
+
additionalInfo?: Omit<ColumnInfo, `type` | `dims`>
|
|
6
|
+
) => Value
|
|
7
|
+
export type Parser = { [key: string]: ParseFunction }
|
|
8
|
+
|
|
9
|
+
const parseNumber = (value: string) => Number(value)
|
|
10
|
+
const parseBool = (value: string) => value === `true` || value === `t`
|
|
11
|
+
const parseBigInt = (value: string) => BigInt(value)
|
|
12
|
+
const parseJson = (value: string) => JSON.parse(value)
|
|
13
|
+
|
|
14
|
+
export const defaultParser: Parser = {
|
|
15
|
+
int2: parseNumber,
|
|
16
|
+
int4: parseNumber,
|
|
17
|
+
int8: parseBigInt,
|
|
18
|
+
bool: parseBool,
|
|
19
|
+
float4: parseNumber,
|
|
20
|
+
float8: parseNumber,
|
|
21
|
+
json: parseJson,
|
|
22
|
+
jsonb: parseJson,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Taken from: https://github.com/electric-sql/pglite/blob/main/packages/pglite/src/types.ts#L233-L279
|
|
26
|
+
export function pgArrayParser(
|
|
27
|
+
value: string,
|
|
28
|
+
parser?: (s: string) => Value
|
|
29
|
+
): Value {
|
|
30
|
+
let i = 0
|
|
31
|
+
let char = null
|
|
32
|
+
let str = ``
|
|
33
|
+
let quoted = false
|
|
34
|
+
let last = 0
|
|
35
|
+
let p: string | undefined = undefined
|
|
36
|
+
|
|
37
|
+
function loop(x: string): Value[] {
|
|
38
|
+
const xs = []
|
|
39
|
+
for (; i < x.length; i++) {
|
|
40
|
+
char = x[i]
|
|
41
|
+
if (quoted) {
|
|
42
|
+
if (char === `\\`) {
|
|
43
|
+
str += x[++i]
|
|
44
|
+
} else if (char === `"`) {
|
|
45
|
+
xs.push(parser ? parser(str) : str)
|
|
46
|
+
str = ``
|
|
47
|
+
quoted = x[i + 1] === `"`
|
|
48
|
+
last = i + 2
|
|
49
|
+
} else {
|
|
50
|
+
str += char
|
|
51
|
+
}
|
|
52
|
+
} else if (char === `"`) {
|
|
53
|
+
quoted = true
|
|
54
|
+
} else if (char === `{`) {
|
|
55
|
+
last = ++i
|
|
56
|
+
xs.push(loop(x))
|
|
57
|
+
} else if (char === `}`) {
|
|
58
|
+
quoted = false
|
|
59
|
+
last < i &&
|
|
60
|
+
xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))
|
|
61
|
+
last = i + 1
|
|
62
|
+
break
|
|
63
|
+
} else if (char === `,` && p !== `}` && p !== `"`) {
|
|
64
|
+
xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i))
|
|
65
|
+
last = i + 1
|
|
66
|
+
}
|
|
67
|
+
p = char
|
|
68
|
+
}
|
|
69
|
+
last < i &&
|
|
70
|
+
xs.push(parser ? parser(x.slice(last, i + 1)) : x.slice(last, i + 1))
|
|
71
|
+
return xs
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return loop(value)[0]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class MessageParser {
|
|
78
|
+
private parser: Parser
|
|
79
|
+
constructor(parser?: Parser) {
|
|
80
|
+
// Merge the provided parser with the default parser
|
|
81
|
+
// to use the provided parser whenever defined
|
|
82
|
+
// and otherwise fall back to the default parser
|
|
83
|
+
this.parser = { ...defaultParser, ...parser }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
parse(messages: string, schema: Schema): Message[] {
|
|
87
|
+
return JSON.parse(messages, (key, value) => {
|
|
88
|
+
// typeof value === `object` is needed because
|
|
89
|
+
// there could be a column named `value`
|
|
90
|
+
// and the value associated to that column will be a string
|
|
91
|
+
if (key === `value` && typeof value === `object`) {
|
|
92
|
+
// Parse the row values
|
|
93
|
+
const row = value as Record<string, Value>
|
|
94
|
+
Object.keys(row).forEach((key) => {
|
|
95
|
+
row[key] = this.parseRow(key, row[key] as string, schema)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
return value
|
|
99
|
+
}) as Message[]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Parses the message values using the provided parser based on the schema information
|
|
103
|
+
private parseRow(key: string, value: string, schema: Schema): Value {
|
|
104
|
+
const columnInfo = schema[key]
|
|
105
|
+
if (!columnInfo) {
|
|
106
|
+
// We don't have information about the value
|
|
107
|
+
// so we just return it
|
|
108
|
+
return value
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Pick the right parser for the type
|
|
112
|
+
const parser = this.parser[columnInfo.type]
|
|
113
|
+
|
|
114
|
+
// Copy the object but don't include `dimensions` and `type`
|
|
115
|
+
const { type: _typ, dims: dimensions, ...additionalInfo } = columnInfo
|
|
116
|
+
|
|
117
|
+
if (dimensions > 0) {
|
|
118
|
+
// It's an array
|
|
119
|
+
const identityParser = (v: string) => v
|
|
120
|
+
return pgArrayParser(value, parser ?? identityParser)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!parser) {
|
|
124
|
+
// No parser was provided for this type of values
|
|
125
|
+
return value
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return parser(value, additionalInfo)
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type Value =
|
|
2
|
+
| string
|
|
3
|
+
| number
|
|
4
|
+
| boolean
|
|
5
|
+
| bigint
|
|
6
|
+
| null
|
|
7
|
+
| Value[]
|
|
8
|
+
| { [key: string]: Value }
|
|
9
|
+
|
|
10
|
+
export type Offset = `-1` | `${number}_${number}`
|
|
11
|
+
|
|
12
|
+
interface Header {
|
|
13
|
+
[key: string]: Value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ControlMessage = {
|
|
17
|
+
headers: Header
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ChangeMessage<T> = {
|
|
21
|
+
key: string
|
|
22
|
+
value: T
|
|
23
|
+
headers: Header & { action: `insert` | `update` | `delete` }
|
|
24
|
+
offset: Offset
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Define the type for a record
|
|
28
|
+
export type Message<T extends Value = { [key: string]: Value }> =
|
|
29
|
+
| ControlMessage
|
|
30
|
+
| ChangeMessage<T>
|
|
31
|
+
|
|
32
|
+
export type RegularColumn = {
|
|
33
|
+
type: string
|
|
34
|
+
dims: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type VarcharColumn = {
|
|
38
|
+
type: `varchar`
|
|
39
|
+
dims: number
|
|
40
|
+
max_length?: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type BpcharColumn = {
|
|
44
|
+
type: `bpchar`
|
|
45
|
+
dims: number
|
|
46
|
+
length?: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type TimeColumn = {
|
|
50
|
+
type: `time` | `timetz` | `timestamp` | `timestamptz`
|
|
51
|
+
dims: number
|
|
52
|
+
precision?: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type IntervalColumn = {
|
|
56
|
+
type: `interval`
|
|
57
|
+
dims: number
|
|
58
|
+
fields?:
|
|
59
|
+
| `YEAR`
|
|
60
|
+
| `MONTH`
|
|
61
|
+
| `DAY`
|
|
62
|
+
| `HOUR`
|
|
63
|
+
| `MINUTE`
|
|
64
|
+
| `YEAR TO MONTH`
|
|
65
|
+
| `DAY TO HOUR`
|
|
66
|
+
| `DAY TO MINUTE`
|
|
67
|
+
| `DAY TO SECOND`
|
|
68
|
+
| `HOUR TO MINUTE`
|
|
69
|
+
| `HOUR TO SECOND`
|
|
70
|
+
| `MINUTE TO SECOND`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type IntervalColumnWithPrecision = {
|
|
74
|
+
type: `interval`
|
|
75
|
+
dims: number
|
|
76
|
+
precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
|
77
|
+
fields?: `SECOND`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type BitColumn = {
|
|
81
|
+
type: `bit`
|
|
82
|
+
dims: number
|
|
83
|
+
length: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type NumericColumn = {
|
|
87
|
+
type: `numeric`
|
|
88
|
+
dims: number
|
|
89
|
+
precision?: number
|
|
90
|
+
scale?: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ColumnInfo =
|
|
94
|
+
| RegularColumn
|
|
95
|
+
| VarcharColumn
|
|
96
|
+
| BpcharColumn
|
|
97
|
+
| TimeColumn
|
|
98
|
+
| IntervalColumn
|
|
99
|
+
| IntervalColumnWithPrecision
|
|
100
|
+
| BitColumn
|
|
101
|
+
| NumericColumn
|
|
102
|
+
|
|
103
|
+
export type Schema = { [key: string]: ColumnInfo }
|
|
104
|
+
|
|
105
|
+
export type TypedMessages<T extends Value = { [key: string]: Value }> = {
|
|
106
|
+
messages: Array<Message<T>>
|
|
107
|
+
schema: ColumnInfo
|
|
108
|
+
}
|