Skip to content

Commit 086a755

Browse files
committed
add synchronisation of object stores
1 parent 7fa1b39 commit 086a755

File tree

7 files changed

+145
-69
lines changed

7 files changed

+145
-69
lines changed

example/sub-process.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import IPOS from '../lib/main.js'
22

3-
const sharedObject = IPOS.new()
4-
console.log(sharedObject.exampleArray)
3+
const sharedObject = await IPOS.new()
4+
console.log('sharedObject.exampleArray', sharedObject.exampleArray)
5+
// console.log(sharedObject.exampleArray[0], /*...sharedObject.exampleObject.entries()*/)

src/init-child.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import IPOSMessaging from './messaging.js'
2+
import IPOS from './main.js'
3+
4+
export default function initChild(this: IPOS) {
5+
let resolve: Function
6+
const promise = new Promise(res => resolve = res)
7+
8+
this.messaging = new IPOSMessaging(process)
9+
this.messaging?.listenForType('sync', message => {
10+
if (!message.fields) return
11+
Object.entries(
12+
IPOS.deserialize(JSON.parse(message.fields))
13+
)
14+
.map(([key, value]: [string, any]) => {
15+
this.create(key, value.constructor())
16+
if (Array.isArray(value)) {
17+
this.get(key)?.push(...value)
18+
}
19+
})
20+
resolve()
21+
})
22+
// register with parent process
23+
this.messaging.send('register')
24+
25+
return promise
26+
}

src/intercept.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import InterceptedArray from './intercept/array.js'
2+
3+
export default function intercept(value: object, interceptCallback: (object: object, method: string, ...args: any) => void): object {
4+
if (Array.isArray(value)) {
5+
value = new InterceptedArray(interceptCallback)
6+
}
7+
/*Object.getOwnPropertyNames(Object.getPrototypeOf(value))
8+
.filter(methodName => !(methodName.startsWith('__') && methodName.endsWith('__')))
9+
.forEach(methodName => {
10+
if (!value[methodName]) return
11+
const method = value[methodName]
12+
value[methodName] = function (...args: any) {
13+
// interception
14+
console.log(methodName)
15+
method.call(value, ...args)
16+
}
17+
})
18+
*/
19+
return value
20+
}

src/array.ts renamed to src/intercept/array.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export default class InterceptedArray<T> extends Array {
2-
private callback
2+
private readonly callback
33

44
// intercepts only methods that change the object
55
constructor(callback: (object: object, method: string, ...args: any) => void) {
6-
super();
6+
super()
77
this.callback = callback
88
}
99

src/main.ts

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
import {deserialize, serialize} from 'v8'
21
import {ChildProcess} from 'child_process'
3-
import InterceptedArray from './array.js'
2+
import initChild from './init-child.js'
43
import IPOSMessaging from './messaging.js'
4+
import {deserialize, serialize} from './serialize.js'
5+
import intercept from './intercept.js'
56

67
export default class IPOS {
7-
private fields: Map<string, object>
8+
private readonly fields: Map<string, object>
89
private fieldsReverseMap: Map<object, string>
9-
private processes: Set<ChildProcess>
1010
private processMessagingMap: Map<ChildProcess, IPOSMessaging>
1111
private readonly proxy
12-
private messaging?: IPOSMessaging;
12+
protected messaging?: IPOSMessaging
1313

14-
static new(): IPOS {
15-
return new IPOS()
14+
static new(): IPOS | Promise<IPOS> {
15+
const ipos = new IPOS()
16+
// was called on child process
17+
if (process.send) {
18+
return new Promise(async resolve => {
19+
await initChild.call(ipos)
20+
resolve(ipos)
21+
})
22+
}
23+
return ipos
1624
}
1725

1826
constructor() {
1927
this.fields = new Map()
2028
this.fieldsReverseMap = new Map()
21-
this.processes = new Set()
2229
this.processMessagingMap = new Map()
2330

2431
// proxy makes all "target.fields" available as "actual" fields
@@ -31,39 +38,21 @@ export default class IPOS {
3138
}
3239
}
3340
})
34-
// was called on child process
35-
if (process.send) {
36-
this.messaging?.listenForType('sync', message => {
37-
console.log(message)
38-
if (message.fields)
39-
this.fields = deserialize(message.fields)
40-
})
41-
42-
// register with parent process
43-
this.messaging = new IPOSMessaging(process)
44-
this.messaging.send('register')
45-
}
4641
return this.proxy
4742
}
4843

44+
public get(key: string): any {
45+
return this.fields.get(key)
46+
}
47+
48+
// todo: also accept and update non object values
4949
public create(key: string, value: object): void {
50-
if (Array.isArray(value)) {
51-
value = new InterceptedArray((object, method, ...args) =>
50+
// console.log('create', key)
51+
if (typeof value === 'object')
52+
value = intercept(value, (object, method, ...args) =>
5253
this.sendMethodCall(object, method, ...args)
5354
)
54-
}
55-
/*Object.getOwnPropertyNames(Object.getPrototypeOf(value))
56-
.filter(methodName => !(methodName.startsWith('__') && methodName.endsWith('__')))
57-
.forEach(methodName => {
58-
if (!value[methodName]) return
59-
const method = value[methodName]
60-
value[methodName] = function (...args: any) {
61-
// interception
62-
console.log(methodName)
63-
method.call(value, ...args)
64-
}
65-
})
66-
*/
55+
6756
this.fields.set(key, value)
6857
this.fieldsReverseMap.set(value, key)
6958
// todo: send update message
@@ -84,16 +73,14 @@ export default class IPOS {
8473
if (registered) return
8574
registered = true
8675

87-
this.processes.add(process)
8876
this.processMessagingMap.set(process, messaging)
8977
this.syncProcess(process)
9078
})
9179
}
9280

9381
private syncProcess(process: ChildProcess) {
94-
console.log('sending sync')
9582
this.processMessagingMap.get(process)?.send('sync', {
96-
field: serialize(this.fields)
83+
fields: JSON.stringify(IPOS.serialize(this.fields))
9784
})
9885
}
9986

@@ -107,30 +94,14 @@ export default class IPOS {
10794
})
10895
}
10996

110-
// serializes types, that "JSON.stringify()" doesn't properly handle
111-
/*private static serialize(value: any): any | void {
112-
// todo: handle other builtins
113-
if (['string', 'number'].includes(typeof value)) {
114-
return value
115-
} else if (typeof value === 'function') {
116-
return value.toString()
117-
} else if (Array.isArray(value)) {
118-
return value.map(serialize)
119-
} else if (value.constructor === {}.constructor) {
120-
return Object.fromEntries(
121-
Array.from(
122-
Object.entries(value)
123-
.map(([key, value]) =>
124-
[key, this.serialize(value)]
125-
)
126-
)
127-
)
128-
} else {
129-
if (!value.stringify && !value.serialize)
130-
throw new Error(
131-
`Class: \`${value.constructor.name}\` must have methods to serialize and deserialize objects. (\`.stringify()\`, \`.serialize()\`)`
132-
)
133-
// return value.toString()
134-
}
135-
}*/
97+
/**
98+
* Serializes types that "JSON.stringify()" doesn't properly handle
99+
*/
100+
public static serialize(value: any): any | void {
101+
return serialize(value)
102+
}
103+
104+
public static deserialize(value: string | number | Array<any> | { $$iposType?: string, data: any }): any | void {
105+
return deserialize(value)
106+
}
136107
}

src/messaging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ type iposMessagingCallback = (message: iposMessagingMessage) => (any | void)
55
type iposMessagingMessage = {
66
protocol: 'ipos',
77
type: iposMessagingType,
8-
fields?: Buffer
8+
fields?: string
99
}
1010

1111
const mustHaveSendError = new Error(`Process must have a \`.send()\` method.`)

src/serialize.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export function serialize(value: any): any | void {
2+
// todo: handle other builtins
3+
if (['string', 'number'].includes(typeof value)) {
4+
return value
5+
} else if (typeof value === 'function') {
6+
return {
7+
$$iposType: 'Function',
8+
data: value.toString()
9+
}
10+
} else if (Array.isArray(value)) {
11+
return value.map(serialize)
12+
} else if (value.constructor === {}.constructor) {
13+
return Object.fromEntries(
14+
Array.from(
15+
Object.entries(value)
16+
.map(([key, value]) =>
17+
[key, serialize(value)]
18+
)
19+
)
20+
)
21+
} else if (value.constructor === Map) {
22+
return {
23+
$$iposType: 'Map',
24+
data: Array.from(value.entries()).map(serialize)
25+
}
26+
} else {
27+
if (!value.stringify && !value.serialize)
28+
throw new Error(
29+
`Class: \`${value.constructor.name}\` must have methods to serialize and deserialize objects. (\`.stringify()\`, \`.serialize()\`)`
30+
)
31+
// return value.toString()
32+
}
33+
}
34+
35+
export function deserialize(value: string | number | Array<any> | { $$iposType?: string, data: any }): any | void {
36+
if (['string', 'number'].includes(typeof value)) {
37+
return value
38+
} else if (Array.isArray(value)) {
39+
return value.map(deserialize)
40+
} else if (typeof value === 'object') {
41+
if (!value.$$iposType) {
42+
return Object.fromEntries(
43+
Array.from(
44+
Object.entries(value)
45+
.map(([key, value]) =>
46+
[key, deserialize(value)]
47+
)
48+
)
49+
)
50+
} else if (value.$$iposType === 'Function') {
51+
// todo: is this acceptable?
52+
return eval(`(${value.data})`)
53+
} else if (value.$$iposType === 'Map') {
54+
return Object.fromEntries(value.data.map(deserialize))
55+
}
56+
} else
57+
console.warn('I don\'t know', value)
58+
}

0 commit comments

Comments
 (0)