@tsplc/debug 0.2.16 → 0.2.18

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/manifest.ts CHANGED
@@ -9,178 +9,180 @@ import { z } from "zod";
9
9
 
10
10
  /** A location in the project's own sources: `file` indexes `sources`; lines and columns are 1-based. */
11
11
  export const sourceRange = z.object({
12
- file: z.number().int(),
13
- line: z.number().int(),
14
- column: z.number().int(),
15
- endLine: z.number().int(),
16
- endColumn: z.number().int(),
12
+ file: z.number().int(),
13
+ line: z.number().int(),
14
+ column: z.number().int(),
15
+ endLine: z.number().int(),
16
+ endColumn: z.number().int(),
17
17
  });
18
18
 
19
19
  const json: z.ZodType<unknown> = z.lazy(() =>
20
- z.union([
21
- z.string(),
22
- z.number(),
23
- z.boolean(),
24
- z.null(),
25
- z.array(json),
26
- z.record(z.string(), json),
27
- ]),
20
+ z.union([
21
+ z.string(),
22
+ z.number(),
23
+ z.boolean(),
24
+ z.null(),
25
+ z.array(json),
26
+ z.record(z.string(), json),
27
+ ]),
28
28
  );
29
29
 
30
30
  export const debugField = z.object({
31
- name: z.string(),
32
- target: z.string(),
33
- type: z.string(),
34
- declaration: sourceRange.optional(),
35
- /** Compiler-made storage, such as an async job's `step`; hidden unless asked for. */
36
- generated: z.literal(true).optional(),
31
+ name: z.string(),
32
+ target: z.string(),
33
+ type: z.string(),
34
+ declaration: sourceRange.optional(),
35
+ /** Compiler-made storage, such as an async job's `step`; hidden unless asked for. */
36
+ generated: z.literal(true).optional(),
37
37
  });
38
38
 
39
39
  /** Keys of `types` are opaque ids; scalars use their IEC name, such as `BOOL`. */
40
40
  export const debugType = z.discriminatedUnion("kind", [
41
- z.object({ kind: z.literal("scalar"), iec: z.string() }),
42
- z.object({
43
- kind: z.literal("enum"),
44
- name: z.string(),
45
- members: z.record(z.string(), z.number().int()),
46
- }),
47
- z.object({
48
- kind: z.literal("record"),
49
- name: z.string(),
50
- declaration: sourceRange.optional(),
51
- fields: z.array(debugField),
52
- }),
53
- z.object({
54
- kind: z.literal("array"),
55
- element: z.string(),
56
- length: z.number().int(),
57
- /** The first index; TypeScript paths use the target's own indexes, `lower` to `lower + length - 1`. */
58
- lower: z.number().int(),
59
- }),
41
+ z.object({ kind: z.literal("scalar"), iec: z.string() }),
42
+ z.object({
43
+ kind: z.literal("enum"),
44
+ name: z.string(),
45
+ members: z.record(z.string(), z.number().int()),
46
+ }),
47
+ z.object({
48
+ kind: z.literal("record"),
49
+ name: z.string(),
50
+ declaration: sourceRange.optional(),
51
+ fields: z.array(debugField),
52
+ }),
53
+ z.object({
54
+ kind: z.literal("array"),
55
+ element: z.string(),
56
+ length: z.number().int(),
57
+ /** The first index; TypeScript paths use the target's own indexes, `lower` to `lower + length - 1`. */
58
+ lower: z.number().int(),
59
+ }),
60
60
  ]);
61
61
 
62
62
  export const debugRoot = z.object({
63
- path: z.string(),
64
- target: z.string(),
65
- type: z.string(),
66
- declaration: sourceRange.optional(),
67
- /** Readable at runtime through the target's adapter; for CODESYS, all stored state is. */
68
- readable: z.boolean(),
69
- generated: z.literal(true).optional(),
63
+ path: z.string(),
64
+ target: z.string(),
65
+ type: z.string(),
66
+ declaration: sourceRange.optional(),
67
+ /** Readable at runtime through the target's adapter; for CODESYS, all stored state is. */
68
+ readable: z.boolean(),
69
+ generated: z.literal(true).optional(),
70
70
  });
71
71
 
72
72
  /** A field the compiler folded away: it has no runtime storage, but its value is known. */
73
73
  export const debugConstant = z.object({
74
- path: z.string(),
75
- type: z.string(),
76
- value: json,
77
- declaration: sourceRange.optional(),
74
+ path: z.string(),
75
+ type: z.string(),
76
+ value: json,
77
+ declaration: sourceRange.optional(),
78
78
  });
79
79
 
80
80
  export const debugIo = z.object({
81
- path: z.string(),
82
- target: z.string(),
83
- direction: z.enum(["input", "output"]),
84
- type: z.string(),
85
- device: z.string().optional(),
86
- channel: z.string().optional(),
87
- address: z.string().optional(),
88
- inverted: z.literal(true).optional(),
89
- shared: z.literal(true).optional(),
81
+ path: z.string(),
82
+ target: z.string(),
83
+ direction: z.enum(["input", "output"]),
84
+ type: z.string(),
85
+ device: z.string().optional(),
86
+ channel: z.string().optional(),
87
+ address: z.string().optional(),
88
+ inverted: z.literal(true).optional(),
89
+ shared: z.literal(true).optional(),
90
90
  });
91
91
 
92
92
  export const debugChannel = z.object({
93
- id: z.string(),
94
- name: z.string(),
95
- direction: z.enum(["input", "output"]),
96
- type: z.string(),
97
- offsetBits: z.number().int(),
98
- bits: z.number().int(),
99
- address: z.string().optional(),
100
- unit: z.string().optional(),
101
- description: z.string().optional(),
93
+ id: z.string(),
94
+ name: z.string(),
95
+ direction: z.enum(["input", "output"]),
96
+ type: z.string(),
97
+ offsetBits: z.number().int(),
98
+ bits: z.number().int(),
99
+ address: z.string().optional(),
100
+ unit: z.string().optional(),
101
+ description: z.string().optional(),
102
102
  });
103
103
 
104
104
  export const debugDevice = z.object({
105
- id: z.string(),
106
- product: z.string().optional(),
107
- vendor: z.string().optional(),
108
- productCode: z.string().optional(),
109
- network: z.object({ address: z.string(), rpiMs: z.number().optional() }).optional(),
110
- connection: z.string().optional(),
111
- assemblies: z.array(
112
- z.object({
113
- direction: z.enum(["input", "output"]),
114
- bytes: z.number().int(),
115
- address: z.string().optional(),
116
- /** A byte array over the whole assembly, mapped or not. */
117
- raw: z.string().optional(),
118
- }),
119
- ),
120
- /**
121
- * Target paths of the driver's own state, such as a generated adapter's `eState`; absent means
122
- * the device's health is unknown.
123
- */
124
- health: z
125
- .object({
126
- state: z.string().optional(),
127
- states: z.record(z.string(), z.number().int()).optional(),
128
- diagnostic: z.string().optional(),
129
- message: z.string().optional(),
130
- })
131
- .optional(),
132
- /** Status symbols by label: hand-configured ones, and a generated Modbus server's error. */
133
- status: z.record(z.string(), z.string()).optional(),
134
- channels: z.array(debugChannel),
105
+ id: z.string(),
106
+ product: z.string().optional(),
107
+ vendor: z.string().optional(),
108
+ productCode: z.string().optional(),
109
+ network: z
110
+ .object({ address: z.string(), rpiMs: z.number().optional() })
111
+ .optional(),
112
+ connection: z.string().optional(),
113
+ assemblies: z.array(
114
+ z.object({
115
+ direction: z.enum(["input", "output"]),
116
+ bytes: z.number().int(),
117
+ address: z.string().optional(),
118
+ /** A byte array over the whole assembly, mapped or not. */
119
+ raw: z.string().optional(),
120
+ }),
121
+ ),
122
+ /**
123
+ * Target paths of the driver's own state, such as a generated adapter's `eState`; absent means
124
+ * the device's health is unknown.
125
+ */
126
+ health: z
127
+ .object({
128
+ state: z.string().optional(),
129
+ states: z.record(z.string(), z.number().int()).optional(),
130
+ diagnostic: z.string().optional(),
131
+ message: z.string().optional(),
132
+ })
133
+ .optional(),
134
+ /** Status symbols by label: hand-configured ones, and a generated Modbus server's error. */
135
+ status: z.record(z.string(), z.string()).optional(),
136
+ channels: z.array(debugChannel),
135
137
  });
136
138
 
137
139
  /** A specialized function: one per method per instance, so `reads` resolve to concrete paths. */
138
140
  export const debugFunction = z.object({
139
- id: z.number().int(),
140
- name: z.string(),
141
- declaration: sourceRange,
142
- /** The TypeScript path of `this`, for a method specialized to one instance. */
143
- instance: z.string().optional(),
144
- task: z.string().optional(),
145
- target: z.string(),
146
- /** Member reads in the body whose runtime path is static in this specialization. */
147
- reads: z.array(z.object({ range: sourceRange, path: z.string() })),
141
+ id: z.number().int(),
142
+ name: z.string(),
143
+ declaration: sourceRange,
144
+ /** The TypeScript path of `this`, for a method specialized to one instance. */
145
+ instance: z.string().optional(),
146
+ task: z.string().optional(),
147
+ target: z.string(),
148
+ /** Member reads in the body whose runtime path is static in this specialization. */
149
+ reads: z.array(z.object({ range: sourceRange, path: z.string() })),
148
150
  });
149
151
 
150
152
  export const debugJob = z.object({
151
- id: z.number().int(),
152
- name: z.string(),
153
- declaration: sourceRange,
154
- instance: z.string().optional(),
155
- /** The job's state, with fields `step`, `busy`, `done`, `error` and `cancelling`. */
156
- target: z.string(),
157
- steps: z.array(
158
- z.object({
159
- step: z.number().int(),
160
- kind: z.enum(["await", "return", "throw", "statement"]),
161
- range: sourceRange,
162
- text: z.string(),
163
- }),
164
- ),
165
- /**
166
- * The PLC clock in milliseconds when the job last started: the clock of telemetry records'
167
- * `time` and a batch's `now`.
168
- */
169
- startedAt: z.string().optional(),
170
- /**
171
- * Service calls the job awaits, its nested jobs' included; `sequence` is the request counter sent
172
- * as `x-tsplc-request`, with `job`'s id when a nested job makes the call.
173
- */
174
- requests: z.array(
175
- z.object({
176
- service: z.string(),
177
- target: z.string(),
178
- sequence: z.string(),
179
- job: z.number().int().optional(),
180
- }),
181
- ),
182
- /** Made by the compiler, such as a service call's own job; hidden unless asked for. */
183
- generated: z.literal(true).optional(),
153
+ id: z.number().int(),
154
+ name: z.string(),
155
+ declaration: sourceRange,
156
+ instance: z.string().optional(),
157
+ /** The job's state, with fields `step`, `busy`, `done`, `error` and `cancelling`. */
158
+ target: z.string(),
159
+ steps: z.array(
160
+ z.object({
161
+ step: z.number().int(),
162
+ kind: z.enum(["await", "return", "throw", "statement"]),
163
+ range: sourceRange,
164
+ text: z.string(),
165
+ }),
166
+ ),
167
+ /**
168
+ * The PLC clock in milliseconds when the job last started: the clock of telemetry records'
169
+ * `time` and a batch's `now`.
170
+ */
171
+ startedAt: z.string().optional(),
172
+ /**
173
+ * Service calls the job awaits, its nested jobs' included; `sequence` is the request counter sent
174
+ * as `x-tsplc-request`, with `job`'s id when a nested job makes the call.
175
+ */
176
+ requests: z.array(
177
+ z.object({
178
+ service: z.string(),
179
+ target: z.string(),
180
+ sequence: z.string(),
181
+ job: z.number().int().optional(),
182
+ }),
183
+ ),
184
+ /** Made by the compiler, such as a service call's own job; hidden unless asked for. */
185
+ generated: z.literal(true).optional(),
184
186
  });
185
187
 
186
188
  /**
@@ -188,71 +190,75 @@ export const debugJob = z.object({
188
190
  * different value, or a job starting or finishing. Trace records name their site by `id`.
189
191
  */
190
192
  export const debugEventSite = z.object({
191
- id: z.number().int(),
192
- kind: z.enum(["state", "decision", "job"]),
193
- /** For `kind: "job"`: the job, and whether it starts or finishes at this site. */
194
- job: z.number().int().optional(),
195
- phase: z.enum(["started", "finished"]).optional(),
196
- /** The TypeScript path written, or of the job. */
197
- path: z.string(),
198
- function: z.number().int().optional(),
199
- type: z.string(),
200
- range: sourceRange,
201
- text: z.string(),
202
- task: z.string(),
203
- /**
204
- * Guard values captured in the same scan, at the write: each `if` condition leaf enclosing it.
205
- * `stable` is false when the body between the guard and the write can change the leaf.
206
- */
207
- operands: z.array(
208
- z.object({
209
- text: z.string(),
210
- range: sourceRange,
211
- type: z.string(),
212
- path: z.string().optional(),
213
- stable: z.boolean(),
214
- }),
215
- ),
193
+ id: z.number().int(),
194
+ kind: z.enum(["state", "decision", "job"]),
195
+ /** For `kind: "job"`: the job, and whether it starts or finishes at this site. */
196
+ job: z.number().int().optional(),
197
+ phase: z.enum(["started", "finished"]).optional(),
198
+ /** The TypeScript path written, or of the job. */
199
+ path: z.string(),
200
+ function: z.number().int().optional(),
201
+ type: z.string(),
202
+ range: sourceRange,
203
+ text: z.string(),
204
+ task: z.string(),
205
+ /**
206
+ * Guard values captured in the same scan, at the write: each `if` condition leaf enclosing it.
207
+ * `stable` is false when the body between the guard and the write can change the leaf.
208
+ */
209
+ operands: z.array(
210
+ z.object({
211
+ text: z.string(),
212
+ range: sourceRange,
213
+ type: z.string(),
214
+ path: z.string().optional(),
215
+ stable: z.boolean(),
216
+ }),
217
+ ),
216
218
  });
217
219
 
218
220
  /** The statement that last wrote each output, per scan. */
219
221
  export const debugWriter = z.object({
220
- output: z.string(),
221
- target: z.string(),
222
- sites: z.array(
223
- z.object({ id: z.number().int(), range: sourceRange, function: z.number().int() }),
224
- ),
222
+ output: z.string(),
223
+ target: z.string(),
224
+ sites: z.array(
225
+ z.object({
226
+ id: z.number().int(),
227
+ range: sourceRange,
228
+ function: z.number().int(),
229
+ }),
230
+ ),
225
231
  });
226
232
 
227
233
  export const debugTask = z.object({
228
- name: z.string(),
229
- intervalMs: z.number(),
230
- priority: z.number().int(),
234
+ name: z.string(),
235
+ intervalMs: z.number(),
236
+ priority: z.number().int(),
231
237
  });
232
238
 
233
239
  export const debugManifest = z.object({
234
- kind: z.literal("tsplc-debug"),
235
- version: z.literal(1),
236
- build: z.object({
237
- id: z.string(),
238
- /** A variable holding `id`, to tell which build the controller runs. */
239
- target: z.string(),
240
- compiler: z.string(),
241
- }),
242
- /** Project-relative paths. */
243
- sources: z.array(z.string()),
244
- types: z.record(z.string(), debugType),
245
- roots: z.array(debugRoot),
246
- constants: z.array(debugConstant),
247
- io: z.array(debugIo),
248
- devices: z.array(debugDevice),
249
- functions: z.array(debugFunction),
250
- jobs: z.array(debugJob),
251
- events: z.array(debugEventSite),
252
- writers: z.array(debugWriter),
253
- tasks: z.array(debugTask),
254
- /** Job error codes by number: 1 cancelled, 2 timeout… */
255
- errors: z.record(z.string(), z.string()),
240
+ kind: z.literal("tsplc-debug"),
241
+ version: z.literal(1),
242
+ build: z.object({
243
+ id: z.string(),
244
+ /** A variable holding `id`, to tell which build the controller runs. */
245
+ target: z.string(),
246
+ compiler: z.string(),
247
+ }),
248
+ /** Project-relative paths. */
249
+ sources: z.array(z.string()),
250
+ types: z.record(z.string(), debugType),
251
+ roots: z.array(debugRoot),
252
+ constants: z.array(debugConstant),
253
+ io: z.array(debugIo),
254
+ devices: z.array(debugDevice),
255
+ functions: z.array(debugFunction),
256
+ jobs: z.array(debugJob),
257
+ events: z.array(debugEventSite),
258
+ writers: z.array(debugWriter),
259
+ tasks: z.array(debugTask),
260
+ /** Job error codes by number: 1 cancelled, 2 timeout… */
261
+ errors: z.record(z.string(), z.string()),
256
262
  });
257
263
 
258
264
  export type SourceRange = z.infer<typeof sourceRange>;
package/src/opcua.ts CHANGED
@@ -1,33 +1,31 @@
1
- import { resolve } from "node:path";
2
- import { createMonitor, type MonitorOptions, type Reading } from "@tsplc/opcua";
1
+ import {
2
+ createMonitor,
3
+ monitorConnection,
4
+ type MonitorOptions,
5
+ type Reading,
6
+ } from "@tsplc/opcua";
3
7
  import type { ResolvedTsplcConfig } from "@tsplc/config";
4
8
  import type { TargetAdapter, TargetRead, TargetSample } from "./adapter.ts";
5
9
 
6
- type MonitorConfig = NonNullable<ResolvedTsplcConfig["monitor"]>;
7
-
8
- /**
9
- * The connection the config's `monitor` section describes. Its certificate directory is relative
10
- * to `root`, and the password comes from `passwordEnv` in `env`.
11
- */
10
+ /** The config's `monitor` connection, with the user's password from `passwordEnv` in `env`. */
12
11
  export const opcUaConnection = (
13
- monitor: MonitorConfig,
14
- options: { root: string; env: Record<string, string | undefined> },
12
+ monitor: NonNullable<ResolvedTsplcConfig["monitor"]>,
13
+ options: { root: string; env: Record<string, string | undefined> },
15
14
  ): MonitorOptions => {
16
- const password = monitor.passwordEnv && options.env[monitor.passwordEnv];
17
- if (monitor.username && !password)
18
- throw new Error(
19
- monitor.passwordEnv
20
- ? `Set ${monitor.passwordEnv} to the OPC UA password of ${monitor.username}.`
21
- : "Configure monitor.passwordEnv for the OPC UA user.",
22
- );
23
- return {
24
- endpoint: monitor.endpoint,
25
- namespaceUri: monitor.namespaceUri,
26
- certificateDirectory: resolve(options.root, monitor.certificateDirectory),
27
- security: monitor.securityPolicy,
28
- securityMode: monitor.securityMode === "Sign" ? "Sign" : "SignAndEncrypt",
29
- ...(monitor.username && password && { credentials: { username: monitor.username, password } }),
30
- };
15
+ const password = monitor.passwordEnv
16
+ ? options.env[monitor.passwordEnv]
17
+ : undefined;
18
+ if (monitor.username && !password) {
19
+ throw new Error(
20
+ monitor.passwordEnv
21
+ ? `Set ${monitor.passwordEnv} to the OPC UA password of ${monitor.username}.`
22
+ : "Configure monitor.passwordEnv for the OPC UA user.",
23
+ );
24
+ }
25
+ return monitorConnection(monitor, {
26
+ root: options.root,
27
+ password: password ?? null,
28
+ });
31
29
  };
32
30
 
33
31
  /**
@@ -35,35 +33,37 @@ export const opcUaConnection = (
35
33
  * what the build publishes, so it suits a local virtual PLC; samples arrive on change.
36
34
  */
37
35
  export const createOpcUaAdapter = (options: MonitorOptions): TargetAdapter => {
38
- const monitor = createMonitor(options);
39
- const controller = new AbortController();
40
- const running = monitor.run(controller.signal);
41
- const sample = ({ value, quality, status }: Reading): TargetSample => ({
42
- value,
43
- quality,
44
- status,
45
- receivedAt: new Date().toISOString(),
46
- });
47
- const names = (targets: readonly TargetRead[]) => targets.map(({ target }) => target);
36
+ const monitor = createMonitor(options);
37
+ const controller = new AbortController();
38
+ const running = monitor.run(controller.signal);
39
+ const sample = ({ value, quality, status }: Reading): TargetSample => ({
40
+ value,
41
+ quality,
42
+ status,
43
+ receivedAt: new Date().toISOString(),
44
+ });
45
+ const names = (targets: readonly TargetRead[]) =>
46
+ targets.map(({ target }) => target);
48
47
 
49
- return {
50
- method: "OPC UA",
51
- endpoint: options.endpoint,
52
- samplingMs: monitor.samplingMs,
53
- connection: monitor.status,
54
- onConnection: monitor.onStatus,
55
- read: async (targets) =>
56
- new Map(
57
- Object.entries(await monitor.read(names(targets))).map(([target, reading]) => [
58
- target,
59
- sample(reading),
60
- ]),
61
- ),
62
- subscribe: (targets, listener) =>
63
- monitor.watch(names(targets), (target, reading) => listener(target, sample(reading))),
64
- close: async () => {
65
- controller.abort();
66
- await running;
67
- },
68
- };
48
+ return {
49
+ method: "OPC UA",
50
+ endpoint: options.endpoint,
51
+ samplingMs: monitor.samplingMs,
52
+ connection: monitor.status,
53
+ onConnection: monitor.onStatus,
54
+ read: async (targets) =>
55
+ new Map(
56
+ Object.entries(await monitor.read(names(targets))).map(
57
+ ([target, reading]) => [target, sample(reading)],
58
+ ),
59
+ ),
60
+ subscribe: (targets, listener) =>
61
+ monitor.watch(names(targets), (target, reading) =>
62
+ listener(target, sample(reading)),
63
+ ),
64
+ close: async () => {
65
+ controller.abort();
66
+ await running;
67
+ },
68
+ };
69
69
  };