@devscholar/node-ps1-dotnet 0.0.1 → 0.0.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/src/proxy.ts ADDED
@@ -0,0 +1,330 @@
1
+ import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.js';
2
+
3
+ export let node_ps1_dotnetGetter: (() => any) | null = null;
4
+
5
+ export function setNodePs1Dotnet(fn: () => any) {
6
+ node_ps1_dotnetGetter = fn;
7
+ }
8
+
9
+ export function getNodePs1Dotnet() {
10
+ if (node_ps1_dotnetGetter) {
11
+ return node_ps1_dotnetGetter();
12
+ }
13
+ throw new Error('node_ps1_dotnet not initialized');
14
+ }
15
+
16
+ const gcRegistry = new FinalizationRegistry((id: string) => {
17
+ try { getNodePs1Dotnet()._release(id); } catch {}
18
+ });
19
+
20
+ export const callbackRegistry = new Map<string, Function>();
21
+ export const typeMetadataCache = new Map<string, Map<string, string>>();
22
+ export const globalTypeCache = new Map<string, Map<string, string>>();
23
+ export const pendingInspectRequests = new Map<string, string[]>();
24
+ export const pendingInspectTimeout = new Map<string, NodeJS.Timeout>();
25
+ export const typeNameCache = new Map<string, string>();
26
+ export const LARGE_ARRAY_THRESHOLD = 50;
27
+
28
+ export function getObjectTypeName(id: string): string | null {
29
+ const ipc = getIpc();
30
+ if (typeNameCache.has(id)) {
31
+ return typeNameCache.get(id)!;
32
+ }
33
+ try {
34
+ const res = ipc!.send({ action: 'GetTypeName', targetId: id });
35
+ if (res && res.typeName) {
36
+ typeNameCache.set(id, res.typeName);
37
+ return res.typeName;
38
+ }
39
+ } catch {}
40
+ return null;
41
+ }
42
+
43
+ export function getTypeMembers(typeName: string, memberNames: string[]): Map<string, string> | null {
44
+ const ipc = getIpc();
45
+ if (globalTypeCache.has(typeName)) {
46
+ const cached = globalTypeCache.get(typeName)!;
47
+ const result = new Map<string, string>();
48
+ let allFound = true;
49
+ for (const name of memberNames) {
50
+ if (cached.has(name)) {
51
+ result.set(name, cached.get(name)!);
52
+ } else {
53
+ allFound = false;
54
+ }
55
+ }
56
+ if (allFound) return result;
57
+ }
58
+
59
+ try {
60
+ const res = ipc!.send({ action: 'InspectType', typeName, memberNames });
61
+ if (res && res.members) {
62
+ if (!globalTypeCache.has(typeName)) {
63
+ globalTypeCache.set(typeName, new Map());
64
+ }
65
+ const cache = globalTypeCache.get(typeName)!;
66
+ for (const [name, type] of Object.entries(res.members as Record<string, string>)) {
67
+ cache.set(name, type);
68
+ }
69
+ return new Map(Object.entries(res.members as Record<string, string>));
70
+ }
71
+ } catch {}
72
+ return null;
73
+ }
74
+
75
+ export function ensureMemberTypeCached(id: string, memberName: string, memberCache: Map<string, string>) {
76
+ if (memberCache.has(memberName)) return;
77
+
78
+ const typeName = getObjectTypeName(id);
79
+ if (typeName) {
80
+ const members = getTypeMembers(typeName, [memberName]);
81
+ if (members && members.has(memberName)) {
82
+ memberCache.set(memberName, members.get(memberName)!);
83
+ return;
84
+ }
85
+ }
86
+
87
+ const ipc = getIpc();
88
+ try {
89
+ const inspectRes = ipc!.send({ action: 'Inspect', targetId: id, memberName });
90
+ memberCache.set(memberName, inspectRes.memberType);
91
+ } catch {
92
+ memberCache.set(memberName, 'property');
93
+ }
94
+ }
95
+
96
+ export function createLazyArray(arr: any[]): any {
97
+ return new Proxy(arr, {
98
+ get(target, prop) {
99
+ if (typeof prop === 'symbol') return target[prop];
100
+ const index = Number(prop);
101
+ if (!isNaN(index) && index >= 0 && index < target.length) {
102
+ return createProxy(target[index]);
103
+ }
104
+ if (prop === 'length') return target.length;
105
+ if (prop === 'map' || prop === 'filter' || prop === 'forEach' || prop === 'reduce') {
106
+ return (...args: any[]) => {
107
+ const transformed = target.map((item: any) => createProxy(item));
108
+ const method = (transformed as any)[prop];
109
+ return method.call(transformed, ...args);
110
+ };
111
+ }
112
+ if (prop === 'slice') {
113
+ return (...args: any[]) => {
114
+ const sliced = target.slice(...args);
115
+ return sliced.map((item: any) => createProxy(item));
116
+ };
117
+ }
118
+ return undefined;
119
+ }
120
+ });
121
+ }
122
+
123
+ export function createProxyWithInlineProps(meta: any): any {
124
+ if (meta.type !== 'ref') return createProxy(meta);
125
+
126
+ const ipc = getIpc();
127
+ const id = meta.id!;
128
+ const inlineProps = meta.props || {};
129
+
130
+ if (!typeMetadataCache.has(id)) {
131
+ typeMetadataCache.set(id, new Map());
132
+ }
133
+ const memberCache = typeMetadataCache.get(id)!;
134
+
135
+ class Stub {}
136
+
137
+ const proxy = new Proxy(Stub, {
138
+ get: (target: any, prop: string) => {
139
+ if (prop === '__ref') return id;
140
+ if (prop === '__inlineProps') return inlineProps;
141
+ if (typeof prop !== 'string') return undefined;
142
+
143
+ if (inlineProps.hasOwnProperty(prop)) {
144
+ memberCache.set(prop, 'property');
145
+ return inlineProps[prop];
146
+ }
147
+
148
+ if (prop.startsWith('add_')) {
149
+ const eventName = prop.substring(4);
150
+ return (callback: Function) => {
151
+ const cbId = `cb_${Date.now()}_${Math.random()}`;
152
+ callbackRegistry.set(cbId, callback);
153
+ ipc!.send({ action: 'AddEvent', targetId: id, eventName, callbackId: cbId });
154
+ };
155
+ }
156
+
157
+ let memType = memberCache.get(prop);
158
+
159
+ if (!memType) {
160
+ ensureMemberTypeCached(id, prop, memberCache);
161
+ memType = memberCache.get(prop);
162
+ }
163
+
164
+ if (memType === 'property') {
165
+ const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [] });
166
+ return createProxy(res);
167
+ } else {
168
+ return (...args: any[]) => {
169
+ const netArgs = args.map((a: any) => {
170
+ if (a && a.__ref) return { __ref: a.__ref };
171
+ if (typeof a === 'function') {
172
+ const cbId = `cb_arg_${Date.now()}_${Math.random()}`;
173
+ callbackRegistry.set(cbId, a);
174
+ return { type: 'callback', callbackId: cbId };
175
+ }
176
+ return a;
177
+ });
178
+ const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: netArgs });
179
+ return createProxy(res);
180
+ };
181
+ }
182
+ },
183
+
184
+ set: (target: any, prop: string, value: any) => {
185
+ if (typeof prop !== 'string') return false;
186
+ const netArg = (value && value.__ref) ? { __ref: value.__ref } : value;
187
+ ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [netArg] });
188
+ memberCache.set(prop, 'property');
189
+ return true;
190
+ },
191
+
192
+ construct: (target: any, args: any[]) => {
193
+ const netArgs = args.map((a: any) => {
194
+ if (a && a.__ref) return { __ref: a.__ref };
195
+ if (typeof a === 'function') {
196
+ const cbId = `cb_ctor_${Date.now()}_${Math.random()}`;
197
+ callbackRegistry.set(cbId, a);
198
+ return { type: 'callback', callbackId: cbId };
199
+ }
200
+ return a;
201
+ });
202
+ const res = ipc!.send({ action: 'New', typeId: id, args: netArgs });
203
+ return createProxy(res);
204
+ },
205
+
206
+ apply: () => { throw new Error("Cannot call .NET object as a function. Need 'new'?"); }
207
+ });
208
+
209
+ gcRegistry.register(proxy, id);
210
+ return proxy;
211
+ }
212
+
213
+ export function createProxy(meta: any): any {
214
+ const ipc = getIpc();
215
+
216
+ if (meta.type === 'primitive' || meta.type === 'null') return meta.value;
217
+
218
+ if (meta.type === 'array') {
219
+ const arr = meta.value;
220
+ if (arr.length <= LARGE_ARRAY_THRESHOLD) {
221
+ return arr.map((item: any) => createProxy(item));
222
+ }
223
+ return createLazyArray(arr);
224
+ }
225
+
226
+ if (meta.type === 'task') {
227
+ const taskId = meta.id;
228
+ return new Promise((resolve, reject) => {
229
+ try {
230
+ const res = ipc!.send({ action: 'AwaitTask', taskId: taskId });
231
+ resolve(createProxy(res));
232
+ } catch (e) {
233
+ reject(e);
234
+ } finally {
235
+ try { ipc!.send({ action: 'Release', targetId: taskId }); } catch {}
236
+ }
237
+ });
238
+ }
239
+
240
+ if (meta.type === 'namespace') {
241
+ const nsName = meta.value;
242
+ const dotnet = getNodePs1Dotnet();
243
+ return new Proxy({}, {
244
+ get: (target: any, prop: string) => {
245
+ if (typeof prop !== 'string') return undefined;
246
+ return dotnet._load(`${nsName}.${prop}`);
247
+ }
248
+ });
249
+ }
250
+
251
+ if (meta.type !== 'ref') return null;
252
+
253
+ const id = meta.id!;
254
+
255
+ if (!typeMetadataCache.has(id)) {
256
+ typeMetadataCache.set(id, new Map());
257
+ }
258
+ const memberCache = typeMetadataCache.get(id)!;
259
+
260
+ class Stub {}
261
+
262
+ const proxy = new Proxy(Stub, {
263
+ get: (target: any, prop: string) => {
264
+ if (prop === '__ref') return id;
265
+ if (typeof prop !== 'string') return undefined;
266
+
267
+ if (prop.startsWith('add_')) {
268
+ const eventName = prop.substring(4);
269
+ return (callback: Function) => {
270
+ const cbId = `cb_${Date.now()}_${Math.random()}`;
271
+ callbackRegistry.set(cbId, callback);
272
+ ipc!.send({ action: 'AddEvent', targetId: id, eventName, callbackId: cbId });
273
+ };
274
+ }
275
+
276
+ let memType = memberCache.get(prop);
277
+
278
+ if (!memType) {
279
+ ensureMemberTypeCached(id, prop, memberCache);
280
+ memType = memberCache.get(prop);
281
+ }
282
+
283
+ if (memType === 'property') {
284
+ const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [] });
285
+ return createProxy(res);
286
+ } else {
287
+ return (...args: any[]) => {
288
+ const netArgs = args.map((a: any) => {
289
+ if (a && a.__ref) return { __ref: a.__ref };
290
+ if (typeof a === 'function') {
291
+ const cbId = `cb_arg_${Date.now()}_${Math.random()}`;
292
+ callbackRegistry.set(cbId, a);
293
+ return { type: 'callback', callbackId: cbId };
294
+ }
295
+ return a;
296
+ });
297
+ const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: netArgs });
298
+ return createProxy(res);
299
+ };
300
+ }
301
+ },
302
+
303
+ set: (target: any, prop: string, value: any) => {
304
+ if (typeof prop !== 'string') return false;
305
+ const netArg = (value && value.__ref) ? { __ref: value.__ref } : value;
306
+ ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [netArg] });
307
+ memberCache.set(prop, 'property');
308
+ return true;
309
+ },
310
+
311
+ construct: (target: any, args: any[]) => {
312
+ const netArgs = args.map((a: any) => {
313
+ if (a && a.__ref) return { __ref: a.__ref };
314
+ if (typeof a === 'function') {
315
+ const cbId = `cb_ctor_${Date.now()}_${Math.random()}`;
316
+ callbackRegistry.set(cbId, a);
317
+ return { type: 'callback', callbackId: cbId };
318
+ }
319
+ return a;
320
+ });
321
+ const res = ipc!.send({ action: 'New', typeId: id, args: netArgs });
322
+ return createProxy(res);
323
+ },
324
+
325
+ apply: () => { throw new Error("Cannot call .NET object as a function. Need 'new'?"); }
326
+ });
327
+
328
+ gcRegistry.register(proxy, id);
329
+ return proxy;
330
+ }
package/src/state.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { IpcSync } from './ipc.js';
2
+ import * as cp from 'node:child_process';
3
+
4
+ let _ipc: IpcSync | null = null;
5
+ let _proc: cp.ChildProcess | null = null;
6
+ let _initialized = false;
7
+ let _cachedRuntimeInfo: { frameworkMoniker: string; runtimeVersion: string } | null = null;
8
+
9
+ export function getIpc() { return _ipc; }
10
+ export function getProc() { return _proc; }
11
+ export function getInitialized() { return _initialized; }
12
+ export function getCachedRuntimeInfo() { return _cachedRuntimeInfo; }
13
+
14
+ export function setIpc(val: IpcSync | null) { _ipc = val; }
15
+ export function setProc(val: cp.ChildProcess | null) { _proc = val; }
16
+ export function setInitialized(val: boolean) { _initialized = val; }
17
+ export function setCachedRuntimeInfo(val: { frameworkMoniker: string; runtimeVersion: string } | null) { _cachedRuntimeInfo = val; }
package/src/types.ts CHANGED
@@ -13,6 +13,8 @@ export interface ProtocolResponse {
13
13
  runtimeVersion?: string;
14
14
  resolvedPath?: string;
15
15
  props?: Record<string, any>;
16
+ typeName?: string;
17
+ members?: Record<string, string>;
16
18
  }
17
19
 
18
20
  export interface CommandRequest {
package/test-utils.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { jest } from '@jest/globals';
2
+
3
+ let cachedDotnet: any = null;
4
+ let isInitialized = false;
5
+
6
+ export async function setupDotnet(): Promise<any> {
7
+ if (cachedDotnet && isInitialized) {
8
+ return cachedDotnet;
9
+ }
10
+
11
+ try {
12
+ cachedDotnet = await import('../src/index.js');
13
+ await cachedDotnet.node_ps1_dotnet._load('System');
14
+ isInitialized = true;
15
+ return cachedDotnet;
16
+ } catch (e) {
17
+ console.log('Failed to setup dotnet:', e);
18
+ return null;
19
+ }
20
+ }
21
+
22
+ export function teardownDotnet(dotnet: any): void {
23
+ try {
24
+ dotnet?.node_ps1_dotnet?._close();
25
+ } catch {}
26
+ isInitialized = false;
27
+ cachedDotnet = null;
28
+ }
29
+
30
+ export function skipIfNotWindows(): void {
31
+ if (process.platform !== 'win32') {
32
+ throw new Error('Skipped: Windows only test');
33
+ }
34
+ }
35
+
36
+ export function createDotnetTests(name: string, testFn: (dotnet: any, System: any) => void): void {
37
+ describe(name, () => {
38
+ let dotnet: any;
39
+ let System: any;
40
+
41
+ beforeAll(async () => {
42
+ dotnet = await setupDotnet();
43
+ if (dotnet) {
44
+ System = dotnet.System;
45
+ }
46
+ });
47
+
48
+ afterAll(() => {
49
+ teardownDotnet(dotnet);
50
+ });
51
+
52
+ if (process.platform !== 'win32') {
53
+ it('should skip on non-Windows', () => {
54
+ expect(true).toBe(true);
55
+ });
56
+ } else {
57
+ testFn(dotnet, System);
58
+ }
59
+ });
60
+ }
61
+
62
+ export function isWindows(): boolean {
63
+ return process.platform === 'win32';
64
+ }
package/tsconfig.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "NodeNext",
4
- "target": "esnext",
5
- "moduleResolution": "nodenext",
6
- "allowImportingTsExtensions": true,
7
- "noEmit": true,
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "declaration": true,
8
+ "declarationDir": "./types",
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": false,
8
12
  "esModuleInterop": true,
9
- "strict": true,
10
- "skipLibCheck": true
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true,
17
+ "noEmit": false,
18
+ "allowSyntheticDefaultImports": true
11
19
  },
12
20
  "include": ["src/**/*"],
13
21
  "exclude": ["node_modules", "dist"]
@@ -0,0 +1 @@
1
+ {"root":["./src/index.ts","./src/ipc.ts","./src/types.ts","./src/utils.ts"],"version":"5.9.3"}
@@ -0,0 +1,17 @@
1
+ export declare const __filename: string;
2
+ export declare const __dirname: string;
3
+ export declare const node_ps1_dotnet: {
4
+ _load(typeName: string): any;
5
+ _release(id: string): void;
6
+ _close(): void;
7
+ _getAssembly(assemblyName: string): any;
8
+ _loadAssembly(assemblyName: string): any;
9
+ _getRuntimeInfo(): {
10
+ frameworkMoniker: string;
11
+ runtimeVersion: string;
12
+ };
13
+ };
14
+ declare const dotnetProxy: any;
15
+ export default dotnetProxy;
16
+ export declare function getSystem(): any;
17
+ export declare const System: any;
package/types/ipc.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { ProtocolResponse, CommandRequest } from './types.ts';
2
+ export declare class IpcSync {
3
+ private pipeName;
4
+ private onEvent;
5
+ fd: number;
6
+ private exited;
7
+ private readBuffer;
8
+ private resultBuffer;
9
+ private bufferOffset;
10
+ private bufferLength;
11
+ constructor(pipeName: string, onEvent: (msg: ProtocolResponse) => any);
12
+ private readLineSync;
13
+ private tryConnect;
14
+ connect(): void;
15
+ send(cmd: CommandRequest): ProtocolResponse;
16
+ close(): void;
17
+ }
@@ -0,0 +1,2 @@
1
+ export declare function createNamespaceProxy(assemblyName: string, dotnet: any): any;
2
+ export declare function createExportNamespaceProxy(namespacePrefix: string, dotnet: any): any;
@@ -0,0 +1,16 @@
1
+ export declare let node_ps1_dotnetGetter: (() => any) | null;
2
+ export declare function setNodePs1Dotnet(fn: () => any): void;
3
+ export declare function getNodePs1Dotnet(): any;
4
+ export declare const callbackRegistry: Map<string, Function>;
5
+ export declare const typeMetadataCache: Map<string, Map<string, string>>;
6
+ export declare const globalTypeCache: Map<string, Map<string, string>>;
7
+ export declare const pendingInspectRequests: Map<string, string[]>;
8
+ export declare const pendingInspectTimeout: Map<string, NodeJS.Timeout>;
9
+ export declare const typeNameCache: Map<string, string>;
10
+ export declare const LARGE_ARRAY_THRESHOLD = 50;
11
+ export declare function getObjectTypeName(id: string): string | null;
12
+ export declare function getTypeMembers(typeName: string, memberNames: string[]): Map<string, string> | null;
13
+ export declare function ensureMemberTypeCached(id: string, memberName: string, memberCache: Map<string, string>): void;
14
+ export declare function createLazyArray(arr: any[]): any;
15
+ export declare function createProxyWithInlineProps(meta: any): any;
16
+ export declare function createProxy(meta: any): any;
@@ -0,0 +1,16 @@
1
+ import { IpcSync } from './ipc.js';
2
+ import * as cp from 'node:child_process';
3
+ export declare function getIpc(): IpcSync;
4
+ export declare function getProc(): cp.ChildProcess;
5
+ export declare function getInitialized(): boolean;
6
+ export declare function getCachedRuntimeInfo(): {
7
+ frameworkMoniker: string;
8
+ runtimeVersion: string;
9
+ };
10
+ export declare function setIpc(val: IpcSync | null): void;
11
+ export declare function setProc(val: cp.ChildProcess | null): void;
12
+ export declare function setInitialized(val: boolean): void;
13
+ export declare function setCachedRuntimeInfo(val: {
14
+ frameworkMoniker: string;
15
+ runtimeVersion: string;
16
+ } | null): void;
@@ -0,0 +1,21 @@
1
+ export interface ProtocolResponse {
2
+ type: string;
3
+ value?: any;
4
+ id?: string;
5
+ message?: string;
6
+ args?: any[];
7
+ callbackId?: string;
8
+ memberType?: 'property' | 'method';
9
+ assemblyName?: string;
10
+ assemblyVersion?: string;
11
+ frameworkMoniker?: string;
12
+ runtimeVersion?: string;
13
+ resolvedPath?: string;
14
+ props?: Record<string, any>;
15
+ typeName?: string;
16
+ members?: Record<string, string>;
17
+ }
18
+ export interface CommandRequest {
19
+ action: string;
20
+ [key: string]: any;
21
+ }
@@ -0,0 +1 @@
1
+ export declare function getPowerShellPath(): string;