@arcticnotes/node-wsh 0.0.11 → 0.0.12

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/lib/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { WindowsScriptingHost} from './node/node-wsh.js';
2
+ export { Collection} from './node/utils.js';
@@ -12,73 +12,24 @@ export class WindowsScriptingHost extends EventEmitter {
12
12
  const command = options.command || COMMAND;
13
13
  const args = options.args || ARGS;
14
14
  const scriptFile = options.scriptFile || SCRIPT_FILE;
15
- const trace = options.trace || 0;
16
- return new WindowsScriptingHost( await Syncline.spawn( command, [ ...args, scriptFile], { trace}));
15
+ return new WindowsScriptingHost( await Syncline.spawn( command, [ ...args, scriptFile], options.syncline), options);
17
16
  }
18
17
 
19
18
  #syncline;
20
19
  #closed = false;
21
20
  #finalizer = new FinalizationRegistry( this.#finalized.bind( this));
22
- #ref2proxy = new Map();
23
- #proxy2ref = new WeakMap();
24
- #handler = {
25
-
26
- wsh: this,
27
-
28
- get( target, prop) {
29
- if( prop === Symbol.toPrimitive)
30
- return () => `ref#${ target.ref}`;
31
- const encodedTarget = this.wsh.#encode( target.proxy);
32
- const encodedProp = this.wsh.#encode( prop);
33
- const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'get', encodedTarget, encodedProp])));
34
- switch( output[ 0]) {
35
- case 'value': return this.wsh.#decode( output[ 1]);
36
- case 'error': throw new Error( output[ 1]);
37
- default: throw new Error( `unknown status: ${ output[ 0]}`);
38
- }
39
- },
40
-
41
- set( target, prop, value) {
42
- const encodedTarget = this.wsh.#encode( target.proxy);
43
- const encodedProp = this.wsh.#encode( prop);
44
- const encodedValue = this.wsh.#encode( value);
45
- const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'set', encodedTarget, encodedProp, encodedValue])));
46
- switch( output[ 0]) {
47
- case 'set': return;
48
- case 'error': throw new Error( output[ 1]);
49
- default: throw new Error( `unknown status: ${ output[ 0]}`);
50
- }
51
- },
52
-
53
- apply( target, thisArg, argumentsList) {
54
- const encodedTarget = this.wsh.#encode( target.proxy);
55
- const encodedThisArg = this.wsh.#encode( thisArg);
56
- const encodedArgumentList = this.wsh.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
57
- const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
58
- switch( output[ 0]) {
59
- case 'value': return this.wsh.#decode( output[ 1]);
60
- case 'error': throw new Error( output[ 1]);
61
- default: throw new Error( `unknown status: ${ output[ 0]}`);
62
- }
63
- },
64
-
65
- construct( target, argumentsList) {
66
- const encodedTarget = this.wsh.#encode( target.proxy);
67
- const encodedArgumentList = this.wsh.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
68
- const output = JSON.parse( this.wsh.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
69
- switch( output[ 0]) {
70
- case 'value': return this.wsh.#decode( output[ 1]);
71
- case 'error': throw new Error( output[ 1]);
72
- default: throw new Error( `unknown status: ${ output[ 0]}`);
73
- }
74
- },
75
- };
21
+ #ref2proxy = new Map(); // Map< string, WeakRef< Proxy | <custom-mapped>>>
22
+ #proxy2ref = new WeakMap(); // Map< Proxy | <custom-mapped>, string>
76
23
 
77
- constructor( syncline) {
24
+ constructor( syncline, options) {
78
25
  super();
79
26
  this.#syncline = syncline;
80
- this.#syncline.on( 'stderr', line => console.log( 'wsh:', line));
81
- this.#syncline.on( 'stdout', line => console.log( 'wsh:', line));
27
+ this.#syncline.on( 'stderr', line => console.log( 'wsh-stderr:', line));
28
+ this.#syncline.on( 'stdout', line => console.log( 'wsh-stdout:', line));
29
+ if( options.printSynclineIO) {
30
+ this.#syncline.on( 'input', line => console.log( 'syncline-input:', line));
31
+ this.#syncline.on( 'output', line => console.log( 'syncline-output', line));
32
+ }
82
33
  }
83
34
 
84
35
  get remoteObjects() {
@@ -90,12 +41,12 @@ export class WindowsScriptingHost extends EventEmitter {
90
41
  };
91
42
  }
92
43
 
93
- global( name) {
44
+ global( name, mapper = undefined) {
94
45
  const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'global', name])));
95
46
  switch( output[ 0]) {
96
- case 'value': return this.#decode( output[ 1]);
47
+ case 'value': return this.#decode( output[ 1], mapper);
97
48
  case 'error': throw new Error( output[ 1]);
98
- default: throw new Error( `unknown status: ${ output[ 0]}`);
49
+ default: throw new Error( `unknown status: ${ output[ 0]}`); // bug
99
50
  }
100
51
  }
101
52
 
@@ -104,67 +55,7 @@ export class WindowsScriptingHost extends EventEmitter {
104
55
  await this.#syncline.close();
105
56
  }
106
57
 
107
- #getOrCreate( ref) {
108
- const existingWeakRef = this.#ref2proxy.get( ref);
109
- const existingProxy = existingWeakRef && existingWeakRef.deref();
110
- if( existingProxy)
111
- return existingProxy;
112
-
113
- const target = new RemoteObject( ref);
114
- const newProxy = new Proxy( target, this.#handler);
115
- target.proxy = newProxy;
116
- this.#ref2proxy.set( ref, new WeakRef( newProxy)); // may be overwriting a dead WeakRef
117
- this.#proxy2ref.set( newProxy, ref);
118
- this.#finalizer.register( newProxy, ref);
119
- this.emit( 'ref', ref, newProxy);
120
- return newProxy;
121
- }
122
-
123
- #finalized( ref) {
124
- if( this.#ref2proxy.get( ref).deref() === undefined) // otherwise, it's overwritten by a refreshed proxy
125
- this.#ref2proxy.delete( ref);
126
- if( !this.#closed)
127
- this.#syncline.exchange( JSON.stringify( [ 'unref', ref]));
128
- this.emit( 'unref', ref);
129
- }
130
-
131
- #encode( decoded) {
132
- switch( typeof decoded) {
133
- case 'boolean':
134
- case 'number':
135
- case 'string':
136
- return decoded;
137
- case 'undefined':
138
- return { type: 'undefined'};
139
- case 'object':
140
- if( decoded === null)
141
- return decoded;
142
- if( Array.isArray( decoded)) {
143
- const encoded = [];
144
- for( const item of decoded)
145
- encoded.push( this.#encode( item));
146
- return encoded;
147
- }
148
- const encoded = { type: 'object', value: {}};
149
- for( const [ name, value] of Object.entries( decoded))
150
- encoded.value[ name] = this.#encode( value);
151
- return encoded;
152
- case 'function':
153
- if( decoded instanceof RemoteObject) {
154
- const ref = this.#proxy2ref.get( decoded);
155
- if( ref === undefined) // not because garbage-collected, because clearly `decoded` is still alive
156
- throw new Error( `remote object reference not found: ${ decoded}`);
157
- return { type: 'ref', value: ref};
158
- }
159
- throw new Error( `functions from node cannot be sent: ${ decoded}`);
160
- case 'bigint':
161
- case 'symbol':
162
- default:
163
- throw new Error( `unsupported value: ${ decoded}`);
164
- }
165
- }
166
-
167
- #decode( encoded) {
58
+ #decode( encoded, mapper) {
168
59
  switch( typeof encoded) {
169
60
  case 'boolean':
170
61
  case 'number':
@@ -176,7 +67,7 @@ export class WindowsScriptingHost extends EventEmitter {
176
67
  if( Array.isArray( encoded)) {
177
68
  const decoded = [];
178
69
  for( const item of encoded)
179
- decoded.push( this.#decode( item));
70
+ decoded.push( this.#decode( item, mapper));
180
71
  return decoded;
181
72
  }
182
73
  switch( encoded.type) {
@@ -185,10 +76,10 @@ export class WindowsScriptingHost extends EventEmitter {
185
76
  case 'object':
186
77
  const decoded = {};
187
78
  for( const [ name, value] of Object.entries( encoded.value))
188
- decoded[ name] = this.#decode( value);
79
+ decoded[ name] = this.#decode( value, mapper);
189
80
  return decoded;
190
81
  case 'ref':
191
- return this.#getOrCreate( encoded.value);
82
+ return this.#getOrCreate( encoded.value, mapper);
192
83
  default:
193
84
  throw new Error( `illegal value: ${ encoded}`);
194
85
  }
@@ -200,27 +91,147 @@ export class WindowsScriptingHost extends EventEmitter {
200
91
  throw new Error( `illegal value: ${ encoded}`);
201
92
  }
202
93
  }
94
+
95
+ #encode( decoded) {
96
+ switch( typeof decoded) {
97
+ case 'boolean':
98
+ case 'number':
99
+ case 'string':
100
+ return decoded;
101
+ case 'undefined':
102
+ return { type: 'undefined'};
103
+ case 'object': {
104
+ if( decoded === null)
105
+ return decoded;
106
+ const ref = this.#proxy2ref.get( decoded);
107
+ if( ref !== undefined)
108
+ return { type: 'ref', value: ref};
109
+ if( Array.isArray( decoded)) {
110
+ const encoded = [];
111
+ for( const item of decoded)
112
+ encoded.push( this.#encode( item));
113
+ return encoded;
114
+ }
115
+ const encoded = { type: 'object', value: {}};
116
+ for( const [ name, value] of Object.entries( decoded))
117
+ encoded.value[ name] = this.#encode( value);
118
+ return encoded;
119
+ }
120
+ case 'function': {
121
+ const ref = this.#proxy2ref.get( decoded);
122
+ if( ref !== undefined)
123
+ return { type: 'ref', value: ref};
124
+ throw new Error( `functions from node cannot be sent: ${ decoded}`);
125
+ }
126
+ case 'bigint':
127
+ case 'symbol':
128
+ default:
129
+ throw new Error( `unsupported value: ${ decoded}`);
130
+ }
131
+ }
132
+
133
+ #getOrCreate( ref, mapper) {
134
+ const existingWeakRef = this.#ref2proxy.get( ref);
135
+ const existingProxy = existingWeakRef && existingWeakRef.deref();
136
+ if( existingProxy)
137
+ return existingProxy;
138
+
139
+ const newProxy = new RemoteObject( this.#syncline, this.#encode.bind( this), this.#decode.bind( this), ref, mapper).proxy;
140
+ if( this.#proxy2ref.has( newProxy)) // sanity check, doesn't catch all misbehaving mappers
141
+ throw new Error( `mapper must return new objects: ${ newProxy}`);
142
+ this.#ref2proxy.set( ref, new WeakRef( newProxy)); // may be overwriting a dead WeakRef
143
+ this.#proxy2ref.set( newProxy, ref);
144
+ this.#finalizer.register( newProxy, ref);
145
+ this.emit( 'ref', ref, newProxy);
146
+ return newProxy;
147
+ }
148
+
149
+ #finalized( ref) {
150
+ if( this.#ref2proxy.get( ref).deref() === undefined) // otherwise, it's overwritten by a refreshed proxy
151
+ this.#ref2proxy.delete( ref);
152
+ if( !this.#closed)
153
+ this.#syncline.exchange( JSON.stringify( [ 'unref', ref]));
154
+ this.emit( 'unref', ref);
155
+ }
203
156
  }
204
157
 
205
158
  class RemoteObject extends Function {
206
159
 
160
+ static #handler = {
161
+ get: ( ticket, prop) => ticket.get( prop, undefined),
162
+ set: ( ticket, prop, value) => ticket.set( prop, value),
163
+ apply: ( ticket, thisArg, argumentsList) => ticket.apply( thisArg, argumentsList, undefined),
164
+ construct: ( ticket, argumentsList) => ticket.construct( argumentsList, undefined),
165
+ };
166
+
167
+ #syncline;
168
+ #encode;
169
+ #decode;
207
170
  #ref;
208
171
  #proxy;
209
172
 
210
- constructor( ref) {
173
+ constructor( syncline, encode, decode, ref, mapper) {
211
174
  super();
175
+ this.#syncline = syncline;
176
+ this.#encode = encode;
177
+ this.#decode = decode;
212
178
  this.#ref = ref;
213
- }
214
-
215
- get ref() {
216
- return this.#ref;
179
+ this.#proxy = mapper? mapper( this): this.newProxy();
217
180
  }
218
181
 
219
182
  get proxy() {
220
183
  return this.#proxy;
221
184
  }
222
185
 
223
- set proxy( proxy) {
224
- this.#proxy = proxy;
186
+ newProxy() {
187
+ return new Proxy( this, RemoteObject.#handler);
188
+ }
189
+
190
+ get( prop, mapper) {
191
+ if( prop === Symbol.toPrimitive)
192
+ return () => `ref#${ this.#ref}`;
193
+ const encodedTarget = this.#encode( this.#proxy);
194
+ const encodedProp = this.#encode( prop);
195
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'get', encodedTarget, encodedProp])));
196
+ switch( output[ 0]) {
197
+ case 'value': return this.#decode( output[ 1], mapper);
198
+ case 'error': throw new Error( output[ 1]);
199
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
200
+ }
201
+ }
202
+
203
+ set( prop, value) {
204
+ const encodedTarget = this.#encode( this.#proxy);
205
+ const encodedProp = this.#encode( prop);
206
+ const encodedValue = this.#encode( value);
207
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'set', encodedTarget, encodedProp, encodedValue])));
208
+ switch( output[ 0]) {
209
+ case 'set': return true;
210
+ case 'error': throw new Error( output[ 1]);
211
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
212
+ }
213
+ }
214
+
215
+ apply( thisArg, argumentsList, mapper) {
216
+ const encodedTarget = this.#encode( this.#proxy);
217
+ const encodedThisArg = this.#encode( thisArg);
218
+ const encodedArgumentList = this.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
219
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'apply', encodedTarget, encodedThisArg, encodedArgumentList])));
220
+ switch( output[ 0]) {
221
+ case 'value': return this.#decode( output[ 1], mapper);
222
+ case 'error': throw new Error( output[ 1]);
223
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
224
+ }
225
+ }
226
+
227
+ construct( argumentsList, mapper) {
228
+ const encodedTarget = this.#encode( this.#proxy);
229
+ const encodedArgumentList = this.#encode( [ ...argumentsList]); // argumentsList may not be instanceof Array
230
+ const output = JSON.parse( this.#syncline.exchange( JSON.stringify( [ 'construct', encodedTarget, encodedArgumentList])));
231
+ switch( output[ 0]) {
232
+ case 'value': return this.#decode( output[ 1], mapper);
233
+ case 'error': throw new Error( output[ 1]);
234
+ default: throw new Error( `unknown status: ${ output[ 0]}`);
235
+ }
225
236
  }
226
237
  }
@@ -0,0 +1,24 @@
1
+ export class Collection {
2
+
3
+ #ticket;
4
+ #type;
5
+
6
+ constructor( ticket, type) {
7
+ this.#ticket = ticket;
8
+ this.#type = type;
9
+ }
10
+
11
+ get length() {
12
+ return this.#ticket.get( 'Count');
13
+ }
14
+
15
+ get( indexBase0) {
16
+ return this.#ticket.get( 'Item', t2 => t2.apply.bind( t2))( this, [ indexBase0 + 1], t3 => new this.#type( t3));
17
+ }
18
+
19
+ *[ Symbol.iterator]() {
20
+ const length = this.length; // expect anomoly if the underlying collect is changing
21
+ for( let i = 0; i < length; i++)
22
+ yield this.get( i);
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcticnotes/node-wsh",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "A Node.js package that runs Windows Scripting Host (WSH) as a child process and exposes the resources from the WSH world to the Node.js world",
5
5
  "author": "Paul <paul@arcticnotes.com>",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  ".": "./lib/index.js"
22
22
  },
23
23
  "dependencies": {
24
- "@arcticnotes/syncline": "0.0.3"
24
+ "@arcticnotes/syncline": "0.0.4"
25
25
  },
26
26
  "files": [
27
27
  "/lib/"