zigbee-clusters 2.8.0 → 2.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-clusters",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Zigbee Cluster Library for Node.js",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -89,7 +89,7 @@ function zclTypeToTS(dataType) {
89
89
  * @param {Function} ClusterClass - Cluster class with static ATTRIBUTES/COMMANDS
90
90
  * @returns {object} Cluster definition
91
91
  */
92
- function parseCluster(ClusterClass) {
92
+ function parseCluster(ClusterClass, exportName) {
93
93
  const clusterName = ClusterClass.NAME;
94
94
  const clusterId = ClusterClass.ID;
95
95
  const attributes = [];
@@ -132,7 +132,11 @@ function parseCluster(ClusterClass) {
132
132
  }
133
133
 
134
134
  return {
135
- clusterName, clusterId, attributes, commands,
135
+ exportName,
136
+ clusterName,
137
+ clusterId,
138
+ attributes,
139
+ commands,
136
140
  };
137
141
  }
138
142
 
@@ -141,8 +145,9 @@ function parseCluster(ClusterClass) {
141
145
  * @param {string} clusterName
142
146
  * @returns {string}
143
147
  */
144
- function toInterfaceName(clusterName) {
145
- const name = clusterName.charAt(0).toUpperCase() + clusterName.slice(1);
148
+ function toInterfaceName(cluster) {
149
+ if (cluster.exportName) return cluster.exportName;
150
+ const name = cluster.clusterName.charAt(0).toUpperCase() + cluster.clusterName.slice(1);
146
151
  return `${name}Cluster`;
147
152
  }
148
153
 
@@ -152,7 +157,7 @@ function toInterfaceName(clusterName) {
152
157
  * @returns {string} TypeScript interface code
153
158
  */
154
159
  function generateClusterInterface(cluster) {
155
- const interfaceName = toInterfaceName(cluster.clusterName);
160
+ const interfaceName = toInterfaceName(cluster);
156
161
  const lines = [];
157
162
 
158
163
  // Generate attributes interface
@@ -172,7 +177,10 @@ function generateClusterInterface(cluster) {
172
177
  if (cluster.attributes.length > 0) {
173
178
  const attrNames = cluster.attributes.map(a => `'${a.name}'`).join(' | ');
174
179
  lines.push(` readAttributes<K extends ${attrNames}>(attributeNames: K[], opts?: { timeout?: number }): Promise<Pick<${interfaceName}Attributes, K>>;`);
175
- lines.push(` writeAttributes(attributes: Partial<${interfaceName}Attributes>): Promise<void>;`);
180
+ lines.push(` readAttributes(attributeNames: Array<keyof ${interfaceName}Attributes | number>, opts?: { timeout?: number }): Promise<Partial<${interfaceName}Attributes> & Record<number, unknown>>;`);
181
+ lines.push(` writeAttributes(attributes: Partial<${interfaceName}Attributes>, opts?: { timeout?: number }): Promise<unknown>;`);
182
+ lines.push(` on<K extends keyof ${interfaceName}Attributes & string>(eventName: \`attr.\${K}\`, listener: (value: ${interfaceName}Attributes[K]) => void): this;`);
183
+ lines.push(` once<K extends keyof ${interfaceName}Attributes & string>(eventName: \`attr.\${K}\`, listener: (value: ${interfaceName}Attributes[K]) => void): this;`);
176
184
  }
177
185
 
178
186
  // Add command methods
@@ -188,9 +196,9 @@ function generateClusterInterface(cluster) {
188
196
  const allArgsOptional = cmd.args.every(a => a.tsType === 'Buffer');
189
197
  const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`;
190
198
  // If all args are optional, make the entire args object optional
191
- lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}): Promise<${returnType}>;`);
199
+ lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}, opts?: ClusterCommandOptions): Promise<${returnType}>;`);
192
200
  } else {
193
- lines.push(` ${cmd.name}(): Promise<${returnType}>;`);
201
+ lines.push(` ${cmd.name}(opts?: ClusterCommandOptions): Promise<${returnType}>;`);
194
202
  }
195
203
  }
196
204
 
@@ -202,9 +210,14 @@ function generateClusterInterface(cluster) {
202
210
  /**
203
211
  * Generate the full index.d.ts file
204
212
  * @param {object[]} clusters - Array of parsed cluster definitions
213
+ * @param {Array<{
214
+ * constantName: string;
215
+ * clusterId: number;
216
+ * clusterName: string;
217
+ * }>} clusterDefinitions - Array of CLUSTER definitions used to generate typed CLUSTER exports
205
218
  * @returns {string} Complete TypeScript definitions file
206
219
  */
207
- function generateTypesFile(clusters) {
220
+ function generateTypesFile(clusters, clusterDefinitions) {
208
221
  const lines = [];
209
222
 
210
223
  // Header
@@ -225,30 +238,47 @@ type ConstructorOptions = {
225
238
  endpointDescriptors: EndpointDescriptor[];
226
239
  sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
227
240
  };
241
+
242
+ type ClusterCommandOptions = {
243
+ timeout?: number;
244
+ waitForResponse?: boolean;
245
+ disableDefaultResponse?: boolean;
246
+ };
247
+
248
+ type ZCLNodeConstructorInput = {
249
+ endpointDescriptors?: EndpointDescriptor[];
250
+ sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise<void>;
251
+ handleFrame?: (
252
+ endpointId: number,
253
+ clusterId: number,
254
+ frame: Buffer,
255
+ meta?: unknown
256
+ ) => Promise<void>;
257
+ };
228
258
  `);
229
259
 
230
260
  // Base ZCLNodeCluster interface
231
261
  lines.push(`export interface ZCLNodeCluster extends EventEmitter {
232
- discoverCommandsGenerated(opts?: {
262
+ discoverCommandsGenerated(params?: {
233
263
  startValue?: number;
234
264
  maxResults?: number;
235
- }): Promise<number[]>;
265
+ }, opts?: { timeout?: number }): Promise<(string | number)[]>;
236
266
 
237
- discoverCommandsReceived(opts?: {
267
+ discoverCommandsReceived(params?: {
238
268
  startValue?: number;
239
269
  maxResults?: number;
240
- }): Promise<number[]>;
270
+ }, opts?: { timeout?: number }): Promise<(string | number)[]>;
241
271
 
242
272
  readAttributes(
243
- attributeNames: string[],
273
+ attributes: Array<string | number>,
244
274
  opts?: { timeout?: number }
245
275
  ): Promise<{ [x: string]: unknown }>;
246
276
 
247
- writeAttributes(attributes?: object): Promise<void>;
277
+ writeAttributes(attributes?: object, opts?: { timeout?: number }): Promise<unknown>;
248
278
 
249
- configureReporting(attributes?: object): Promise<void>;
279
+ configureReporting(attributes?: object, opts?: { timeout?: number }): Promise<void>;
250
280
 
251
- readReportingConfiguration(attributes?: (string | number)[]): Promise<{
281
+ readReportingConfiguration(attributes?: (string | number)[], opts?: { timeout?: number }): Promise<{
252
282
  status: string;
253
283
  direction: 'reported' | 'received';
254
284
  attributeId: number;
@@ -259,11 +289,12 @@ type ConstructorOptions = {
259
289
  timeoutPeriod?: number;
260
290
  }[]>;
261
291
 
262
- discoverAttributes(): Promise<(string | number)[]>;
292
+ discoverAttributes(opts?: { timeout?: number }): Promise<(string | number)[]>;
263
293
 
264
- discoverAttributesExtended(): Promise<{
294
+ discoverAttributesExtended(opts?: { timeout?: number }): Promise<{
265
295
  name?: string;
266
296
  id: number;
297
+ dataTypeId: number;
267
298
  acl: { readable: boolean; writable: boolean; reportable: boolean };
268
299
  }[]>;
269
300
  }
@@ -279,12 +310,48 @@ type ConstructorOptions = {
279
310
  lines.push('/** Type-safe cluster registry */');
280
311
  lines.push('export interface ClusterRegistry {');
281
312
  for (const cluster of clusters) {
282
- const interfaceName = toInterfaceName(cluster.clusterName);
313
+ const interfaceName = toInterfaceName(cluster);
283
314
  lines.push(` ${cluster.clusterName}?: ${interfaceName};`);
284
315
  }
285
316
  lines.push('}');
286
317
  lines.push('');
287
318
 
319
+ // Generate cluster type lookup maps for inference helpers
320
+ lines.push('/** Cluster type lookup by cluster NAME */');
321
+ lines.push('export interface ClusterTypeByName {');
322
+ for (const cluster of clusters) {
323
+ const interfaceName = toInterfaceName(cluster);
324
+ lines.push(` ${cluster.clusterName}: ${interfaceName};`);
325
+ }
326
+ lines.push('}');
327
+ lines.push('');
328
+
329
+ // Generate cluster attribute lookup maps for inference helpers
330
+ lines.push('/** Cluster attributes lookup by cluster NAME */');
331
+ lines.push('export interface ClusterAttributesByName {');
332
+ for (const cluster of clusters) {
333
+ const interfaceName = toInterfaceName(cluster);
334
+ const attributesInterfaceName = `${interfaceName}Attributes`;
335
+ const attributesType = cluster.attributes.length > 0 ? attributesInterfaceName : 'Record<string, unknown>';
336
+ lines.push(` ${cluster.clusterName}: ${attributesType};`);
337
+ }
338
+ lines.push('}');
339
+ lines.push('');
340
+
341
+ lines.push('/** Infer a typed cluster interface from a CLUSTER definition object. */');
342
+ lines.push('export type ClusterTypeFromDefinition<TDef extends { NAME: string; ID: number }> =');
343
+ lines.push(" TDef['NAME'] extends keyof ClusterTypeByName");
344
+ lines.push(" ? ClusterTypeByName[TDef['NAME']]");
345
+ lines.push(' : ZCLNodeCluster;');
346
+ lines.push('');
347
+
348
+ lines.push('/** Infer typed cluster attribute map from a CLUSTER definition object. */');
349
+ lines.push('export type ClusterAttributesFromDefinition<TDef extends { NAME: string; ID: number }> =');
350
+ lines.push(" TDef['NAME'] extends keyof ClusterAttributesByName");
351
+ lines.push(" ? ClusterAttributesByName[TDef['NAME']]");
352
+ lines.push(' : Record<string, unknown>;');
353
+ lines.push('');
354
+
288
355
  // Generate endpoint type
289
356
  lines.push(`export type ZCLNodeEndpoint = {
290
357
  clusters: ClusterRegistry & {
@@ -303,23 +370,46 @@ export interface ZCLNode {
303
370
  }
304
371
  `);
305
372
 
306
- // Module declaration for CommonJS compatibility
307
- lines.push(`declare module "zigbee-clusters" {
308
- export const ZCLNode: {
309
- new (options: ConstructorOptions): ZCLNode;
310
- };
311
- export const CLUSTER: {
312
- [key: string]: { ID: number; NAME: string; ATTRIBUTES: unknown; COMMANDS: unknown };
313
- };
314
- export { ZCLNodeCluster };`);
373
+ // Runtime value exports
374
+ lines.push(`export const ZCLNode: {
375
+ new (node: ZCLNodeConstructorInput): ZCLNode;
376
+ };
377
+
378
+ export const CLUSTER: {
379
+ `);
380
+ for (const def of clusterDefinitions) {
381
+ lines.push(` ${def.constantName}: {`);
382
+ lines.push(' ID: number;');
383
+ lines.push(` NAME: '${def.clusterName}';`);
384
+ lines.push(' ATTRIBUTES: unknown;');
385
+ lines.push(' COMMANDS: unknown;');
386
+ lines.push(' };');
387
+ }
388
+ lines.push('};');
315
389
 
316
390
  // Export all cluster classes
317
391
  for (const cluster of clusters) {
318
- const interfaceName = toInterfaceName(cluster.clusterName);
319
- lines.push(` export const ${interfaceName}: ${interfaceName};`);
392
+ const interfaceName = toInterfaceName(cluster);
393
+ const exportName = cluster.exportName || interfaceName;
394
+ lines.push(`export const ${exportName}: {`);
395
+ lines.push(` new (...args: any[]): ${interfaceName};`);
396
+ lines.push(` ID: ${cluster.clusterId};`);
397
+ lines.push(` NAME: '${cluster.clusterName}';`);
398
+ lines.push(' ATTRIBUTES: unknown;');
399
+ lines.push(' COMMANDS: unknown;');
400
+ lines.push('};');
320
401
  }
321
402
 
322
- lines.push('}');
403
+ lines.push('');
404
+ lines.push('declare const _default: {');
405
+ lines.push(' ZCLNode: typeof ZCLNode;');
406
+ lines.push(' CLUSTER: typeof CLUSTER;');
407
+ for (const cluster of clusters) {
408
+ const exportName = cluster.exportName || toInterfaceName(cluster);
409
+ lines.push(` ${exportName}: typeof ${exportName};`);
410
+ }
411
+ lines.push('};');
412
+ lines.push('export default _default;');
323
413
 
324
414
  return lines.join('\n');
325
415
  }
@@ -338,7 +428,7 @@ function main() {
338
428
  for (const [name, value] of Object.entries(clustersModule)) {
339
429
  if (name.endsWith('Cluster') && typeof value === 'function' && value.NAME) {
340
430
  try {
341
- const cluster = parseCluster(value);
431
+ const cluster = parseCluster(value, name);
342
432
  clusters.push(cluster);
343
433
  console.log(` ✓ ${cluster.clusterName} (${cluster.attributes.length} attrs, ${cluster.commands.length} cmds)`);
344
434
  } catch (err) {
@@ -350,8 +440,16 @@ function main() {
350
440
  // Sort clusters alphabetically
351
441
  clusters.sort((a, b) => a.clusterName.localeCompare(b.clusterName));
352
442
 
443
+ const clusterDefinitions = Object.entries(clustersModule.CLUSTER)
444
+ .map(([constantName, value]) => ({
445
+ constantName,
446
+ clusterId: value.ID,
447
+ clusterName: value.NAME,
448
+ }))
449
+ .sort((a, b) => a.constantName.localeCompare(b.constantName));
450
+
353
451
  console.log(`\nGenerating ${OUTPUT_FILE}...`);
354
- const output = generateTypesFile(clusters);
452
+ const output = generateTypesFile(clusters, clusterDefinitions);
355
453
  fs.writeFileSync(OUTPUT_FILE, output);
356
454
 
357
455
  console.log(`Done! Generated types for ${clusters.length} clusters.`);