@bonsae/nrg 0.14.0 → 0.15.0
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 +1 -1
- package/server/index.cjs +323 -238
- package/server/resources/nrg-client.js +1885 -1882
- package/test/index.js +28 -8
- package/types/server.d.ts +104 -41
- package/types/test.d.ts +3 -2
- package/vite/index.js +69 -12
package/test/index.js
CHANGED
|
@@ -182,6 +182,7 @@ function createNodeRedNode(options = {}) {
|
|
|
182
182
|
flow: flowCtx,
|
|
183
183
|
global: globalCtx
|
|
184
184
|
};
|
|
185
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
185
186
|
return {
|
|
186
187
|
id: options.id ?? `node-${Math.random().toString(36).slice(2, 10)}`,
|
|
187
188
|
type: options.type ?? "test-node",
|
|
@@ -195,7 +196,15 @@ function createNodeRedNode(options = {}) {
|
|
|
195
196
|
log: vi.fn(),
|
|
196
197
|
warn: vi.fn(),
|
|
197
198
|
error: vi.fn(),
|
|
198
|
-
on: vi.fn()
|
|
199
|
+
on: vi.fn((event, handler) => {
|
|
200
|
+
if (!handlers.has(event)) handlers.set(event, []);
|
|
201
|
+
handlers.get(event).push(handler);
|
|
202
|
+
}),
|
|
203
|
+
emit: vi.fn(async (event, ...args) => {
|
|
204
|
+
for (const handler of handlers.get(event) ?? []) {
|
|
205
|
+
await handler(...args);
|
|
206
|
+
}
|
|
207
|
+
}),
|
|
199
208
|
send: vi.fn(),
|
|
200
209
|
status: vi.fn(),
|
|
201
210
|
updateWires: vi.fn(),
|
|
@@ -385,6 +394,9 @@ function initValidator(RED) {
|
|
|
385
394
|
});
|
|
386
395
|
}
|
|
387
396
|
|
|
397
|
+
// src/core/server/nodes/symbols.ts
|
|
398
|
+
var WIRE_HANDLERS = Symbol("wireHandlers");
|
|
399
|
+
|
|
388
400
|
// src/test/index.ts
|
|
389
401
|
function buildConfig(NodeClass, userConfig = {}) {
|
|
390
402
|
const defaults = {};
|
|
@@ -409,16 +421,23 @@ function attachHelpers(node, nodeRedNode) {
|
|
|
409
421
|
nodeRedNode.status.mockImplementation((status) => {
|
|
410
422
|
statusCalls.push(status);
|
|
411
423
|
});
|
|
412
|
-
const nodeRef = node;
|
|
413
424
|
const helpers = {
|
|
414
425
|
async receive(msg) {
|
|
415
426
|
const sendFn = vi2.fn((outMsg) => {
|
|
416
427
|
nodeRedNode.send(outMsg);
|
|
417
428
|
});
|
|
418
|
-
|
|
429
|
+
const doneFn = vi2.fn();
|
|
430
|
+
await nodeRedNode.emit("input", msg, sendFn, doneFn);
|
|
431
|
+
if (doneFn.mock.calls[0]?.[0] instanceof Error) {
|
|
432
|
+
throw doneFn.mock.calls[0][0];
|
|
433
|
+
}
|
|
419
434
|
},
|
|
420
435
|
async close(removed = false) {
|
|
421
|
-
|
|
436
|
+
const doneFn = vi2.fn();
|
|
437
|
+
await nodeRedNode.emit("close", removed, doneFn);
|
|
438
|
+
if (doneFn.mock.calls[0]?.[0] instanceof Error) {
|
|
439
|
+
throw doneFn.mock.calls[0][0];
|
|
440
|
+
}
|
|
422
441
|
},
|
|
423
442
|
reset() {
|
|
424
443
|
sentMessages.length = 0;
|
|
@@ -500,12 +519,13 @@ async function createNode(NodeClass, options = {}) {
|
|
|
500
519
|
credentials,
|
|
501
520
|
...overrideOpts
|
|
502
521
|
});
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
);
|
|
522
|
+
NodeClass.validateSettings(RED);
|
|
523
|
+
await Promise.resolve(NodeClass.registered?.(RED));
|
|
506
524
|
const node = new NodeClass(RED, nodeRedNode, config, credentials);
|
|
525
|
+
const createdPromise = Promise.resolve(node.created?.());
|
|
526
|
+
node[WIRE_HANDLERS](nodeRedNode, createdPromise);
|
|
527
|
+
await createdPromise;
|
|
507
528
|
const augmented = attachHelpers(node, nodeRedNode);
|
|
508
|
-
await Promise.resolve(augmented.created?.());
|
|
509
529
|
return { node: augmented, RED };
|
|
510
530
|
}
|
|
511
531
|
export {
|
package/types/server.d.ts
CHANGED
|
@@ -375,8 +375,18 @@ export interface TNodeRef<T = any> extends TSchema {
|
|
|
375
375
|
type ResolveNodeRefs<T> = T extends TypedInput<any> ? T : T extends (...args: any[]) => any ? T : T extends Array<infer Item> ? ResolveNodeRefs<Item>[] : T extends object ? {
|
|
376
376
|
[K in keyof T]: ResolveNodeRefs<T[K]>;
|
|
377
377
|
} : T;
|
|
378
|
-
/**
|
|
379
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Infers the TypeScript type from a schema or a record of schemas.
|
|
380
|
+
*
|
|
381
|
+
* - Single schema: `Infer<typeof MySchema>` → the inferred message type
|
|
382
|
+
* - Record of schemas: `Infer<typeof outputsSchema>` → `{ portName: InferredType }` port map
|
|
383
|
+
*
|
|
384
|
+
* The record form produces a simple mapped type that resolves eagerly,
|
|
385
|
+
* giving `sendToPort()` proper autocomplete in class-based nodes.
|
|
386
|
+
*/
|
|
387
|
+
export type Infer<T extends TSchema | Record<string, TSchema>> = T extends TSchema ? ResolveNodeRefs<Static<T>> : {
|
|
388
|
+
[K in keyof T & string]: T[K] extends TSchema ? ResolveNodeRefs<Static<T[K]>> : never;
|
|
389
|
+
};
|
|
380
390
|
type TypedInputType = (typeof TYPED_INPUT_TYPES)[number];
|
|
381
391
|
/** Schema type representing a Node-RED TypedInput (value + type pair). */
|
|
382
392
|
export interface TTypedInput<T = unknown> extends TSchema {
|
|
@@ -390,7 +400,9 @@ export type Schema<T extends TProperties = TProperties> = TObject<T>;
|
|
|
390
400
|
type InferOr<T, Fallback> = T extends TSchema ? Infer<T> : Fallback;
|
|
391
401
|
type InferOutputs<T> = T extends readonly TSchema[] ? {
|
|
392
402
|
[K in keyof T]: T[K] extends TSchema ? Infer<T[K]> : never;
|
|
393
|
-
} : T extends TSchema ? Infer<T> :
|
|
403
|
+
} : T extends TSchema ? Infer<T> : T extends Record<string, TSchema> ? {
|
|
404
|
+
[K in keyof T & string]: Infer<T[K]>;
|
|
405
|
+
} : any;
|
|
394
406
|
declare const NodeConfigSchema: import("@sinclair/typebox").TObject<{
|
|
395
407
|
id: import("@sinclair/typebox").TString;
|
|
396
408
|
type: import("@sinclair/typebox").TString;
|
|
@@ -414,6 +426,43 @@ declare const IONodeConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
414
426
|
name: import("@sinclair/typebox").TString;
|
|
415
427
|
z: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
416
428
|
}>;
|
|
429
|
+
export declare const ErrorPortSchema: import("@sinclair/typebox").TObject<{
|
|
430
|
+
error: import("@sinclair/typebox").TObject<{
|
|
431
|
+
message: import("@sinclair/typebox").TString;
|
|
432
|
+
source: import("@sinclair/typebox").TObject<{
|
|
433
|
+
id: import("@sinclair/typebox").TString;
|
|
434
|
+
type: import("@sinclair/typebox").TString;
|
|
435
|
+
name: import("@sinclair/typebox").TString;
|
|
436
|
+
}>;
|
|
437
|
+
}>;
|
|
438
|
+
}>;
|
|
439
|
+
export declare const CompletePortSchema: import("@sinclair/typebox").TObject<{
|
|
440
|
+
complete: import("@sinclair/typebox").TObject<{
|
|
441
|
+
source: import("@sinclair/typebox").TObject<{
|
|
442
|
+
id: import("@sinclair/typebox").TString;
|
|
443
|
+
type: import("@sinclair/typebox").TString;
|
|
444
|
+
name: import("@sinclair/typebox").TString;
|
|
445
|
+
}>;
|
|
446
|
+
}>;
|
|
447
|
+
}>;
|
|
448
|
+
export declare const StatusPortSchema: import("@sinclair/typebox").TObject<{
|
|
449
|
+
status: import("@sinclair/typebox").TObject<{
|
|
450
|
+
fill: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[
|
|
451
|
+
import("@sinclair/typebox").TLiteral<"red">,
|
|
452
|
+
import("@sinclair/typebox").TLiteral<"green">
|
|
453
|
+
]>>;
|
|
454
|
+
shape: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[
|
|
455
|
+
import("@sinclair/typebox").TLiteral<"dot">,
|
|
456
|
+
import("@sinclair/typebox").TLiteral<"string">
|
|
457
|
+
]>>;
|
|
458
|
+
text: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
459
|
+
}>;
|
|
460
|
+
source: import("@sinclair/typebox").TObject<{
|
|
461
|
+
id: import("@sinclair/typebox").TString;
|
|
462
|
+
type: import("@sinclair/typebox").TString;
|
|
463
|
+
name: import("@sinclair/typebox").TString;
|
|
464
|
+
}>;
|
|
465
|
+
}>;
|
|
417
466
|
declare function NodeRef<T extends new (...args: any[]) => any>(nodeClass: T, options?: NrgSchemaOptions): TNodeRef<InstanceType<T>>;
|
|
418
467
|
declare function TypedInput$1<T = unknown>(options?: NrgSchemaOptions): TTypedInput<T>;
|
|
419
468
|
/**
|
|
@@ -445,7 +494,7 @@ interface NodeContextStore {
|
|
|
445
494
|
set<T = any>(key: string, value: T): Promise<void>;
|
|
446
495
|
keys(): Promise<string[]>;
|
|
447
496
|
}
|
|
448
|
-
interface NodeConstructor<T = any, TConfig = any, TCredentials = any> {
|
|
497
|
+
export interface NodeConstructor<T = any, TConfig = any, TCredentials = any> {
|
|
449
498
|
readonly type: string;
|
|
450
499
|
readonly category: string;
|
|
451
500
|
readonly color?: string;
|
|
@@ -456,12 +505,13 @@ interface NodeConstructor<T = any, TConfig = any, TCredentials = any> {
|
|
|
456
505
|
readonly credentialsSchema?: Schema;
|
|
457
506
|
readonly settingsSchema?: Schema;
|
|
458
507
|
readonly inputSchema?: Schema;
|
|
459
|
-
readonly outputsSchema?: Schema | Schema[]
|
|
508
|
+
readonly outputsSchema?: Schema | Schema[] | Record<string, Schema>;
|
|
460
509
|
readonly validateInput?: boolean;
|
|
461
510
|
readonly validateOutput?: boolean;
|
|
462
511
|
readonly name: string;
|
|
463
512
|
registered?(RED: RED): void | Promise<void>;
|
|
464
|
-
|
|
513
|
+
register(RED: RED): void | Promise<void>;
|
|
514
|
+
validateSettings(RED: RED): void;
|
|
465
515
|
new (RED: RED, node: NodeRedNode, config: NodeConfig<TConfig>, credentials: NodeCredentials<TCredentials>): T;
|
|
466
516
|
}
|
|
467
517
|
type NodeConfig<TConfig = any> = TConfig & Static<typeof NodeConfigSchema>;
|
|
@@ -485,6 +535,7 @@ interface INode<TConfig = any, TCredentials = any, TSettings = any> {
|
|
|
485
535
|
created?(): void | Promise<void>;
|
|
486
536
|
closed?(removed?: boolean): void | Promise<void>;
|
|
487
537
|
}
|
|
538
|
+
declare const WIRE_HANDLERS: unique symbol;
|
|
488
539
|
/**
|
|
489
540
|
* Abstract base class for all NRG nodes. Provides lifecycle hooks, config
|
|
490
541
|
* validation, logging, timers, i18n, and settings management.
|
|
@@ -493,14 +544,19 @@ interface INode<TConfig = any, TCredentials = any, TSettings = any> {
|
|
|
493
544
|
* for shared configuration nodes.
|
|
494
545
|
*/
|
|
495
546
|
declare abstract class Node$1<TConfig = any, TCredentials = any, TSettings = any> implements INode<TConfig, TCredentials, TSettings> {
|
|
547
|
+
#private;
|
|
496
548
|
static readonly type: string;
|
|
497
549
|
static readonly category: "config" | string;
|
|
498
550
|
static readonly configSchema?: Schema;
|
|
499
551
|
static readonly credentialsSchema?: Schema;
|
|
500
552
|
static readonly settingsSchema?: Schema;
|
|
501
|
-
private static _cachedSettings;
|
|
502
553
|
static registered?(RED: RED): void | Promise<void>;
|
|
503
554
|
static validateSettings(RED: RED): void;
|
|
555
|
+
/**
|
|
556
|
+
* Registers this node class with Node-RED. Handles instance creation,
|
|
557
|
+
* event handler wiring, settings validation, and the user's registered() hook.
|
|
558
|
+
*/
|
|
559
|
+
static register(RED: RED): Promise<void>;
|
|
504
560
|
protected readonly RED: RED;
|
|
505
561
|
protected readonly node: NodeRedNode;
|
|
506
562
|
protected readonly context: ConfigNodeContext | IONodeContext;
|
|
@@ -508,6 +564,7 @@ declare abstract class Node$1<TConfig = any, TCredentials = any, TSettings = any
|
|
|
508
564
|
private readonly timers;
|
|
509
565
|
private readonly intervals;
|
|
510
566
|
constructor(RED: RED, node: NodeRedNode, config: NodeConfig<TConfig>, credentials: NodeCredentials<TCredentials>);
|
|
567
|
+
[WIRE_HANDLERS](nodeRedNode: NodeRedNode, createdPromise: Promise<void>): void;
|
|
511
568
|
i18n(key: string, substitutions?: Record<string, string>): string;
|
|
512
569
|
setTimeout(fn: () => void, ms: number): NodeJS.Timeout;
|
|
513
570
|
setInterval(fn: () => void, ms: number): NodeJS.Timeout;
|
|
@@ -543,29 +600,31 @@ declare abstract class Node$1<TConfig = any, TCredentials = any, TSettings = any
|
|
|
543
600
|
* ```
|
|
544
601
|
*/
|
|
545
602
|
export declare abstract class IONode<TConfig = any, TCredentials = any, TInput = any, TOutput = any, TSettings = any> extends Node$1<TConfig, TCredentials, TSettings> implements IIONode<TConfig, TCredentials, TInput, TOutput, TSettings> {
|
|
603
|
+
#private;
|
|
546
604
|
static readonly align?: "left" | "right";
|
|
547
605
|
static readonly color: HexColor;
|
|
548
606
|
static readonly inputSchema?: Schema;
|
|
549
|
-
static readonly outputsSchema?: Schema | Schema[]
|
|
607
|
+
static readonly outputsSchema?: Schema | Schema[] | Record<string, Schema>;
|
|
550
608
|
static readonly validateInput: boolean;
|
|
551
609
|
static readonly validateOutput: boolean;
|
|
552
610
|
static get inputs(): 0 | 1;
|
|
553
611
|
static get outputs(): number;
|
|
554
|
-
private _send;
|
|
555
612
|
readonly config: IONodeConfig<TConfig>;
|
|
556
613
|
protected readonly context: IONodeContext;
|
|
557
614
|
constructor(RED: RED, node: NodeRedNode, config: IONodeConfig<TConfig>, credentials: IONodeCredentials<TCredentials>);
|
|
615
|
+
[WIRE_HANDLERS](nodeRedNode: NodeRedNode, createdPromise: Promise<void>): void;
|
|
558
616
|
input(msg: TInput): void | Promise<void>;
|
|
559
617
|
send(msg: TOutput): void;
|
|
618
|
+
get baseOutputs(): number;
|
|
619
|
+
get totalOutputs(): number;
|
|
560
620
|
/**
|
|
561
621
|
* Send a message to a specific output port by index or name.
|
|
562
|
-
*
|
|
563
|
-
* based on the node's
|
|
622
|
+
* Built-in ports: `"error"`, `"complete"`, `"status"` — resolved automatically
|
|
623
|
+
* based on the node's built-in port configuration.
|
|
624
|
+
* Custom named ports are resolved from `outputsSchema` when it is a record.
|
|
564
625
|
* Numeric indices refer to the base output ports (0-based).
|
|
565
626
|
*/
|
|
566
|
-
sendToPort(
|
|
567
|
-
private _getEmitPortIndex;
|
|
568
|
-
private _nodeSource;
|
|
627
|
+
sendToPort<P extends (keyof TOutput & string) | number | "error" | "complete" | "status">(port: P, msg: P extends keyof TOutput ? TOutput[P] : unknown): void;
|
|
569
628
|
status(status: IONodeStatus): void;
|
|
570
629
|
error(message: string, msg?: any): void;
|
|
571
630
|
updateWires(wires: string[][]): void;
|
|
@@ -594,8 +653,9 @@ type IONodeContext = {
|
|
|
594
653
|
global: NodeContextStore;
|
|
595
654
|
};
|
|
596
655
|
type HexColor = `#${string}`;
|
|
597
|
-
type BoundIONode<TC extends TSchema | undefined, TCr extends TSchema | undefined, TS extends TSchema | undefined, TIn extends TSchema | undefined, TOut extends TSchema | readonly TSchema[] | undefined> = IONode<InferOr<TC, any>, InferOr<TCr, any>, InferOr<TIn, any>, InferOutputs<TOut>, InferOr<TS, any>>;
|
|
598
|
-
|
|
656
|
+
type BoundIONode<TC extends TSchema | undefined, TCr extends TSchema | undefined, TS extends TSchema | undefined, TIn extends TSchema | undefined, TOut extends TSchema | readonly TSchema[] | Record<string, TSchema> | undefined> = IONode<InferOr<TC, any>, InferOr<TCr, any>, InferOr<TIn, any>, InferOutputs<TOut>, InferOr<TS, any>>;
|
|
657
|
+
/** Public instance interface for IO nodes. Implemented by {@link IONode}. */
|
|
658
|
+
export interface IIONode<TConfig = any, TCredentials = any, TInput = any, TOutput = any, TSettings = any> extends INode<TConfig, TCredentials, TSettings> {
|
|
599
659
|
readonly config: IONodeConfig<TConfig>;
|
|
600
660
|
readonly credentials: IONodeCredentials<TCredentials> | undefined;
|
|
601
661
|
readonly x: number;
|
|
@@ -607,9 +667,11 @@ interface IIONode<TConfig = any, TCredentials = any, TInput = any, TOutput = any
|
|
|
607
667
|
status(status: IONodeStatus): void;
|
|
608
668
|
updateWires(wires: string[][]): void;
|
|
609
669
|
receive(msg: TInput): void;
|
|
610
|
-
|
|
670
|
+
readonly baseOutputs: number;
|
|
671
|
+
readonly totalOutputs: number;
|
|
672
|
+
sendToPort<P extends (keyof TOutput & string) | number | "error" | "complete" | "status">(port: P, msg: P extends keyof TOutput ? TOutput[P] : unknown): void;
|
|
611
673
|
}
|
|
612
|
-
interface IONodeDefinition<TConfigSchema extends TSchema | undefined = undefined, TCredsSchema extends TSchema | undefined = undefined, TSettingsSchema extends TSchema | undefined = undefined, TInputSchema extends TSchema | undefined = undefined, TOutputsSchema extends TSchema | readonly TSchema[] | undefined = undefined> {
|
|
674
|
+
interface IONodeDefinition<TConfigSchema extends TSchema | undefined = undefined, TCredsSchema extends TSchema | undefined = undefined, TSettingsSchema extends TSchema | undefined = undefined, TInputSchema extends TSchema | undefined = undefined, TOutputsSchema extends TSchema | readonly TSchema[] | Record<string, TSchema> | undefined = undefined> {
|
|
613
675
|
type: string;
|
|
614
676
|
category?: string;
|
|
615
677
|
color?: HexColor;
|
|
@@ -656,7 +718,8 @@ type ConfigNodeContext = {
|
|
|
656
718
|
global: NodeContextStore;
|
|
657
719
|
};
|
|
658
720
|
type BoundConfigNode<TC extends TSchema | undefined, TCr extends TSchema | undefined, TS extends TSchema | undefined> = ConfigNode<InferOr<TC, any>, InferOr<TCr, any>, InferOr<TS, any>>;
|
|
659
|
-
|
|
721
|
+
/** Public instance interface for config nodes. Implemented by {@link ConfigNode}. */
|
|
722
|
+
export interface IConfigNode<TConfig = any, TCredentials = any, TSettings = any> extends INode<TConfig, TCredentials, TSettings> {
|
|
660
723
|
readonly config: ConfigNodeConfig<TConfig>;
|
|
661
724
|
readonly credentials: ConfigNodeCredentials<TCredentials> | undefined;
|
|
662
725
|
readonly userIds: string[];
|
|
@@ -672,6 +735,27 @@ interface ConfigNodeDefinition<TConfigSchema extends TSchema | undefined = undef
|
|
|
672
735
|
created?(this: BoundConfigNode<TConfigSchema, TCredsSchema, TSettingsSchema>): void | Promise<void>;
|
|
673
736
|
closed?(this: BoundConfigNode<TConfigSchema, TCredsSchema, TSettingsSchema>, removed?: boolean): void | Promise<void>;
|
|
674
737
|
}
|
|
738
|
+
/**
|
|
739
|
+
* Registers a custom node with Node-RED.
|
|
740
|
+
*
|
|
741
|
+
* @param RED - The Node-RED runtime API object
|
|
742
|
+
* @param NodeClass - A node class extending Node, IONode, or ConfigNode
|
|
743
|
+
* @throws If NodeClass does not extend Node
|
|
744
|
+
* @throws If NodeClass.type is not defined
|
|
745
|
+
*/
|
|
746
|
+
export declare function registerType(RED: RED, NodeClass: NodeConstructor): Promise<void>;
|
|
747
|
+
type RegistrationFunction = ((RED: RED) => Promise<void>) & {
|
|
748
|
+
nodes: NodeConstructor[];
|
|
749
|
+
};
|
|
750
|
+
/**
|
|
751
|
+
* Registers multiple node classes with Node-RED.
|
|
752
|
+
*
|
|
753
|
+
* Returns a Node-RED package function that Node-RED calls with the RED
|
|
754
|
+
* runtime object when loading the package.
|
|
755
|
+
*
|
|
756
|
+
* @param nodes - Array of node classes to register
|
|
757
|
+
*/
|
|
758
|
+
export declare function registerTypes(nodes: NodeConstructor[]): RegistrationFunction;
|
|
675
759
|
/**
|
|
676
760
|
* Creates an IO node class from a definition object. Provides automatic type
|
|
677
761
|
* inference from schemas, reducing boilerplate compared to the class-based API.
|
|
@@ -690,7 +774,7 @@ interface ConfigNodeDefinition<TConfigSchema extends TSchema | undefined = undef
|
|
|
690
774
|
* });
|
|
691
775
|
* ```
|
|
692
776
|
*/
|
|
693
|
-
export declare function defineIONode<TConfigSchema extends TSchema | undefined = undefined, TCredsSchema extends TSchema | undefined = undefined, TSettingsSchema extends TSchema | undefined = undefined, TInputSchema extends TSchema | undefined = undefined, TOutputsSchema extends TSchema | readonly TSchema[] | undefined = undefined>(def: IONodeDefinition<TConfigSchema, TCredsSchema, TSettingsSchema, TInputSchema, TOutputsSchema>): NodeConstructor<IIONode<InferOr<TConfigSchema, any>, InferOr<TCredsSchema, any>, InferOr<TInputSchema, any>, InferOutputs<TOutputsSchema>>>;
|
|
777
|
+
export declare function defineIONode<TConfigSchema extends TSchema | undefined = undefined, TCredsSchema extends TSchema | undefined = undefined, TSettingsSchema extends TSchema | undefined = undefined, TInputSchema extends TSchema | undefined = undefined, TOutputsSchema extends TSchema | readonly TSchema[] | Record<string, TSchema> | undefined = undefined>(def: IONodeDefinition<TConfigSchema, TCredsSchema, TSettingsSchema, TInputSchema, TOutputsSchema>): NodeConstructor<IIONode<InferOr<TConfigSchema, any>, InferOr<TCredsSchema, any>, InferOr<TInputSchema, any>, InferOutputs<TOutputsSchema>>>;
|
|
694
778
|
/**
|
|
695
779
|
* Creates a config node class from a definition object.
|
|
696
780
|
*
|
|
@@ -708,27 +792,6 @@ export declare function defineConfigNode<TConfigSchema extends TSchema | undefin
|
|
|
708
792
|
export declare class NrgError extends Error {
|
|
709
793
|
constructor(message: string);
|
|
710
794
|
}
|
|
711
|
-
/**
|
|
712
|
-
* Registers a custom node with Node-RED.
|
|
713
|
-
*
|
|
714
|
-
* @param RED - The Node-RED runtime API object
|
|
715
|
-
* @param NodeClass - A node class extending Node, IONode, or ConfigNode
|
|
716
|
-
* @throws If NodeClass does not extend Node
|
|
717
|
-
* @throws If NodeClass.type is not defined
|
|
718
|
-
*/
|
|
719
|
-
export declare function registerType(RED: RED, NodeClass: NodeConstructor): Promise<void>;
|
|
720
|
-
type RegistrationFunction = ((RED: RED) => Promise<void>) & {
|
|
721
|
-
nodes: NodeConstructor[];
|
|
722
|
-
};
|
|
723
|
-
/**
|
|
724
|
-
* Registers multiple node classes with Node-RED.
|
|
725
|
-
*
|
|
726
|
-
* Returns a Node-RED package function that Node-RED calls with the RED
|
|
727
|
-
* runtime object when loading the package.
|
|
728
|
-
*
|
|
729
|
-
* @param nodes - Array of node classes to register
|
|
730
|
-
*/
|
|
731
|
-
export declare function registerTypes(nodes: NodeConstructor[]): RegistrationFunction;
|
|
732
795
|
/** Defines the set of nodes exported by a Node-RED package. */
|
|
733
796
|
export interface ModuleDefinition {
|
|
734
797
|
nodes: NodeConstructor[];
|
package/types/test.d.ts
CHANGED
|
@@ -214,12 +214,13 @@ interface NodeConstructor<T = any, TConfig = any, TCredentials = any> {
|
|
|
214
214
|
readonly credentialsSchema?: Schema;
|
|
215
215
|
readonly settingsSchema?: Schema;
|
|
216
216
|
readonly inputSchema?: Schema;
|
|
217
|
-
readonly outputsSchema?: Schema | Schema[]
|
|
217
|
+
readonly outputsSchema?: Schema | Schema[] | Record<string, Schema>;
|
|
218
218
|
readonly validateInput?: boolean;
|
|
219
219
|
readonly validateOutput?: boolean;
|
|
220
220
|
readonly name: string;
|
|
221
221
|
registered?(RED: RED): void | Promise<void>;
|
|
222
|
-
|
|
222
|
+
register(RED: RED): void | Promise<void>;
|
|
223
|
+
validateSettings(RED: RED): void;
|
|
223
224
|
new (RED: RED, node: NodeRedNode, config: NodeConfig<TConfig>, credentials: NodeCredentials<TCredentials>): T;
|
|
224
225
|
}
|
|
225
226
|
type NodeConfig<TConfig = any> = TConfig & Static<typeof NodeConfigSchema>;
|
package/vite/index.js
CHANGED
|
@@ -661,18 +661,34 @@ function getSchemaReferences(filePath) {
|
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
663
|
const schemaRefs = /* @__PURE__ */ new Map();
|
|
664
|
-
|
|
664
|
+
const recordPortNames = /* @__PURE__ */ new Map();
|
|
665
|
+
let outputsSchemaIsRecord = false;
|
|
666
|
+
function extractIdentifiers(node, propName) {
|
|
665
667
|
if (ts.isIdentifier(node)) return [node.text];
|
|
666
668
|
if (ts.isArrayLiteralExpression(node)) {
|
|
667
669
|
return node.elements.filter(ts.isIdentifier).map((el) => el.text);
|
|
668
670
|
}
|
|
671
|
+
if (ts.isObjectLiteralExpression(node) && propName === "outputsSchema") {
|
|
672
|
+
const ids = [];
|
|
673
|
+
outputsSchemaIsRecord = true;
|
|
674
|
+
for (const prop of node.properties) {
|
|
675
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer)) {
|
|
676
|
+
const portName = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : void 0;
|
|
677
|
+
if (portName) {
|
|
678
|
+
ids.push(prop.initializer.text);
|
|
679
|
+
recordPortNames.set(prop.initializer.text, portName);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return ids;
|
|
684
|
+
}
|
|
669
685
|
return [];
|
|
670
686
|
}
|
|
671
687
|
for (const stmt of source.statements) {
|
|
672
688
|
if (ts.isClassDeclaration(stmt)) {
|
|
673
689
|
for (const member of stmt.members) {
|
|
674
690
|
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name) && member.name.text in SCHEMA_PROP_SEMANTICS && member.initializer) {
|
|
675
|
-
const ids = extractIdentifiers(member.initializer);
|
|
691
|
+
const ids = extractIdentifiers(member.initializer, member.name.text);
|
|
676
692
|
if (ids.length > 0) {
|
|
677
693
|
schemaRefs.set(member.name.text, ids);
|
|
678
694
|
}
|
|
@@ -686,7 +702,7 @@ function getSchemaReferences(filePath) {
|
|
|
686
702
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
687
703
|
for (const prop of arg.properties) {
|
|
688
704
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text in SCHEMA_PROP_SEMANTICS) {
|
|
689
|
-
const ids = extractIdentifiers(prop.initializer);
|
|
705
|
+
const ids = extractIdentifiers(prop.initializer, prop.name.text);
|
|
690
706
|
if (ids.length > 0) {
|
|
691
707
|
schemaRefs.set(prop.name.text, ids);
|
|
692
708
|
}
|
|
@@ -699,15 +715,17 @@ function getSchemaReferences(filePath) {
|
|
|
699
715
|
const result = [];
|
|
700
716
|
for (const [propName, identifiers] of schemaRefs) {
|
|
701
717
|
const semanticName = SCHEMA_PROP_SEMANTICS[propName];
|
|
702
|
-
const isArray = identifiers.length > 1;
|
|
718
|
+
const isArray = identifiers.length > 1 && !outputsSchemaIsRecord;
|
|
719
|
+
const isRecord = propName === "outputsSchema" && outputsSchemaIsRecord;
|
|
703
720
|
for (const identifier of identifiers) {
|
|
704
721
|
const importSource = importMap.get(identifier);
|
|
705
722
|
if (importSource) {
|
|
706
723
|
result.push({
|
|
707
724
|
localName: identifier,
|
|
708
|
-
semanticName: isArray ? identifier : semanticName,
|
|
725
|
+
semanticName: isArray || isRecord ? identifier : semanticName,
|
|
709
726
|
importSource,
|
|
710
|
-
tupleProp: isArray ? semanticName : void 0
|
|
727
|
+
tupleProp: isArray || isRecord ? semanticName : void 0,
|
|
728
|
+
recordPortName: isRecord ? recordPortNames.get(identifier) : void 0
|
|
711
729
|
});
|
|
712
730
|
}
|
|
713
731
|
}
|
|
@@ -734,10 +752,18 @@ function getFactoryInfo(filePath) {
|
|
|
734
752
|
}
|
|
735
753
|
return null;
|
|
736
754
|
}
|
|
737
|
-
function buildTypeArg(schemaMap, semanticName) {
|
|
755
|
+
function buildTypeArg(schemaMap, semanticName, portNameMap) {
|
|
738
756
|
const names = schemaMap.get(semanticName);
|
|
739
757
|
if (!names || names.length === 0) return "any";
|
|
740
|
-
if (names.length === 1
|
|
758
|
+
if (names.length === 1 && !portNameMap?.has(names[0]))
|
|
759
|
+
return `Infer<typeof ${names[0]}>`;
|
|
760
|
+
if (portNameMap && names.some((n) => portNameMap.has(n))) {
|
|
761
|
+
const entries = names.map((n) => {
|
|
762
|
+
const portName = portNameMap.get(n);
|
|
763
|
+
return portName ? `${portName}: Infer<typeof ${n}>` : null;
|
|
764
|
+
}).filter(Boolean);
|
|
765
|
+
return `{ ${entries.join(", ")} }`;
|
|
766
|
+
}
|
|
741
767
|
return `[${names.map((n) => `Infer<typeof ${n}>`).join(", ")}]`;
|
|
742
768
|
}
|
|
743
769
|
function buildNodeReexports(srcDir, entryFile) {
|
|
@@ -755,12 +781,17 @@ function buildNodeReexports(srcDir, entryFile) {
|
|
|
755
781
|
const interfaceName = factoryInfo.factoryName === "defineIONode" ? "IIONode" : "IConfigNode";
|
|
756
782
|
lines.push(`import _${ns} from "${specifier}";`);
|
|
757
783
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
784
|
+
const portNameMap = /* @__PURE__ */ new Map();
|
|
758
785
|
for (const ref of schemaRefs) {
|
|
759
786
|
const key = ref.tupleProp ?? ref.semanticName;
|
|
760
787
|
if (!schemaMap.has(key)) schemaMap.set(key, []);
|
|
761
788
|
schemaMap.get(key).push(ref.localName);
|
|
789
|
+
if (ref.recordPortName) {
|
|
790
|
+
portNameMap.set(ref.localName, ref.recordPortName);
|
|
791
|
+
}
|
|
762
792
|
}
|
|
763
793
|
const hasSchemas = schemaMap.size > 0;
|
|
794
|
+
const hasRecordOutputs = portNameMap.size > 0;
|
|
764
795
|
const nrgImports = ["NodeConstructor", interfaceName];
|
|
765
796
|
if (hasSchemas) nrgImports.push("Infer");
|
|
766
797
|
lines.push(
|
|
@@ -785,11 +816,16 @@ function buildNodeReexports(srcDir, entryFile) {
|
|
|
785
816
|
}
|
|
786
817
|
let typeArgs;
|
|
787
818
|
if (factoryInfo.factoryName === "defineIONode") {
|
|
819
|
+
const outputsArg = buildTypeArg(
|
|
820
|
+
schemaMap,
|
|
821
|
+
"OutputsSchema",
|
|
822
|
+
portNameMap.size > 0 ? portNameMap : void 0
|
|
823
|
+
);
|
|
788
824
|
typeArgs = [
|
|
789
825
|
buildTypeArg(schemaMap, "ConfigSchema"),
|
|
790
826
|
buildTypeArg(schemaMap, "CredentialsSchema"),
|
|
791
827
|
buildTypeArg(schemaMap, "InputSchema"),
|
|
792
|
-
|
|
828
|
+
outputsArg
|
|
793
829
|
].join(", ");
|
|
794
830
|
} else {
|
|
795
831
|
typeArgs = [
|
|
@@ -1586,9 +1622,10 @@ function generateHelpDoc(nodeClass, labels, t) {
|
|
|
1586
1622
|
if (inputSection) lines.push(inputSection);
|
|
1587
1623
|
}
|
|
1588
1624
|
if (nodeClass.outputsSchema) {
|
|
1589
|
-
|
|
1625
|
+
const os2 = nodeClass.outputsSchema;
|
|
1626
|
+
if (Array.isArray(os2)) {
|
|
1590
1627
|
const portSections = [];
|
|
1591
|
-
|
|
1628
|
+
os2.forEach((schema, i) => {
|
|
1592
1629
|
const title = `${t.sections.port} ${i + 1}`;
|
|
1593
1630
|
const portPropLabels = labels.outputs?.[i];
|
|
1594
1631
|
const section = generateSchemaSection({
|
|
@@ -1604,6 +1641,26 @@ function generateHelpDoc(nodeClass, labels, t) {
|
|
|
1604
1641
|
if (portSections.length) {
|
|
1605
1642
|
lines.push(
|
|
1606
1643
|
`<h3>${t.sections.outputs}</h3>
|
|
1644
|
+
${portSections.join("\n")}`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
} else if (!("type" in os2 || "properties" in os2)) {
|
|
1648
|
+
const portSections = [];
|
|
1649
|
+
for (const [portName, schema] of Object.entries(os2)) {
|
|
1650
|
+
const portPropLabels = labels.outputs?.[portName];
|
|
1651
|
+
const section = generateSchemaSection({
|
|
1652
|
+
title: portName,
|
|
1653
|
+
schema,
|
|
1654
|
+
t,
|
|
1655
|
+
labels: portPropLabels,
|
|
1656
|
+
heading: "####",
|
|
1657
|
+
includeDefault: false
|
|
1658
|
+
});
|
|
1659
|
+
if (section) portSections.push(section);
|
|
1660
|
+
}
|
|
1661
|
+
if (portSections.length) {
|
|
1662
|
+
lines.push(
|
|
1663
|
+
`<h3>${t.sections.outputs}</h3>
|
|
1607
1664
|
${portSections.join("\n")}`
|
|
1608
1665
|
);
|
|
1609
1666
|
}
|
|
@@ -1611,7 +1668,7 @@ ${portSections.join("\n")}`
|
|
|
1611
1668
|
const outputPropLabels = labels.outputs?.[0];
|
|
1612
1669
|
const section = generateSchemaSection({
|
|
1613
1670
|
title: t.sections.output,
|
|
1614
|
-
schema:
|
|
1671
|
+
schema: os2,
|
|
1615
1672
|
t,
|
|
1616
1673
|
labels: outputPropLabels,
|
|
1617
1674
|
includeDefault: false
|