@knime/hub-features 1.26.2 → 1.28.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.
@@ -0,0 +1,158 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const schemaPath = path.join(__dirname, "schema.json");
7
+ const editorOutputPath = path.join(__dirname, "editor-event-functions.ts");
8
+ const cloudHomeOutputPath = path.join(
9
+ __dirname,
10
+ "cloudhome-event-functions.ts",
11
+ );
12
+
13
+ const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
14
+ const events = schema.properties.events.properties;
15
+
16
+ const TYPE_HELPERS = {
17
+ analyticsEvent: "AnalyticsEvent",
18
+ defaultContext: "DefaultContext",
19
+ fnParams: "EventFunctionArgs",
20
+ generatedStaticFields: "GeneratedStaticFields",
21
+ schemaStaticFields: "SchemaStaticFields",
22
+ };
23
+
24
+ const HEADER_LINES = [
25
+ "/* eslint-disable max-lines */",
26
+ "/* eslint-disable camelcase */",
27
+ "/* eslint-disable @typescript-eslint/no-explicit-any */",
28
+ "// AUTO-GENERATED FILE. DO NOT EDIT.",
29
+ `import type { ${Object.values(TYPE_HELPERS).join(", ")} } from "../types";`,
30
+ 'import { toSnakeCaseDeep } from "../utils/toSnakeCaseDeep";',
31
+ ];
32
+ const editorResult = [];
33
+ const cloudHomeResult = [];
34
+
35
+ /**
36
+ * Generic event definition, read from the schema.json.
37
+ * @typedef {{
38
+ * properties: {
39
+ * data: { $ref?: string };
40
+ * [key: string]: any;
41
+ * };
42
+ * $comment?: string;
43
+ * }} EventDefinition
44
+ */
45
+
46
+ /**
47
+ * Generate the JSDoc string for the generated event function, if it has a `$comment` field
48
+ * in the schema
49
+ * @param {EventDefinition} eventDef
50
+ * @returns {String}
51
+ */
52
+ const generateJSDocComment = (eventDef) => {
53
+ // eslint-disable-next-line no-useless-concat
54
+ const jsdoc = "/**\n" + ` * ${eventDef.$comment}\n` + " */";
55
+ return jsdoc;
56
+ };
57
+
58
+ /**
59
+ * Resolves the const value from a property, handling both direct `const` and
60
+ * `allOf`-based patterns like `{ allOf: [{ $ref: "..." }, { const: "value" }] }`
61
+ * @param {{ const?: any; allOf?: Array<{ const?: any }> }} prop
62
+ * @returns {any | undefined}
63
+ */
64
+ const resolveConst = (prop) => {
65
+ if (prop.const !== undefined) {
66
+ return prop.const;
67
+ }
68
+ if (Array.isArray(prop.allOf)) {
69
+ return prop.allOf.find((s) => s.const !== undefined)?.const;
70
+ }
71
+ return undefined;
72
+ };
73
+
74
+ /**
75
+ * Generates an event function for an event based on its name and definition
76
+ * @param {String} fnName
77
+ * @param {EventDefinition} eventDef
78
+ * @param {Boolean} isPayloadRequired whether the function requires a data payload
79
+ * @returns {String} function as a string to be added to the generated file
80
+ */
81
+ const generateEventFunction = (fnName, eventDef, isPayloadRequired = true) => {
82
+ const params = isPayloadRequired
83
+ ? `eventData: ${TYPE_HELPERS.fnParams}<"${fnName}">`
84
+ : "";
85
+
86
+ let fn =
87
+ // function header
88
+ `export const ${fnName} = <T extends ${TYPE_HELPERS.defaultContext}>(ctx: T) => (${params}): ${TYPE_HELPERS.analyticsEvent} => {\n` +
89
+ // open object for static data and add properties that are static but generated at runtime
90
+ " const staticData = {\n" +
91
+ " ...toSnakeCaseDeep(ctx),\n" +
92
+ " unique_event_id: crypto.randomUUID(),\n" +
93
+ " timestamp: new Date().toISOString(),\n";
94
+
95
+ for (const [key, prop] of Object.entries(eventDef.properties)) {
96
+ // add properties that are static and whose values come from the schema
97
+ const constValue = resolveConst(prop);
98
+ if (constValue !== undefined) {
99
+ fn += ` ${key}: "${constValue}",\n`;
100
+ } else if (prop.type === "object" && prop.properties) {
101
+ // nested object (e.g. action): emit only sub-properties that have const values
102
+ fn += ` ${key}: {\n`;
103
+ for (const [subKey, subProp] of Object.entries(prop.properties)) {
104
+ const subConstValue = resolveConst(subProp);
105
+ if (subConstValue !== undefined) {
106
+ fn += ` ${subKey}: "${subConstValue}",\n`;
107
+ }
108
+ }
109
+ fn += " },\n";
110
+ }
111
+ }
112
+
113
+ // close object for staticData
114
+ fn += ` } satisfies ${TYPE_HELPERS.schemaStaticFields} & ${TYPE_HELPERS.generatedStaticFields};\n\n`;
115
+
116
+ const payload = isPayloadRequired
117
+ ? "{ ...staticData, payload: { ...toSnakeCaseDeep(eventData as Record<string, any>) } }"
118
+ : "{ ...staticData }";
119
+
120
+ fn += ` const event = { id: staticData.event_name, data: ${payload} };\n\n`;
121
+ fn += " return event;\n";
122
+ fn += "};\n\n";
123
+
124
+ return fn;
125
+ };
126
+
127
+ /**
128
+ * Checks whether the given event definition has an empty payload or requires data
129
+ * @param {EventDefinition} eventDef
130
+ * @returns
131
+ */
132
+ const hasEmptyPayload = (eventDef) => {
133
+ return Boolean(eventDef.properties.payload?.$ref?.includes("EmptyPayload"));
134
+ };
135
+
136
+ for (const [fnName, eventDef] of Object.entries(events)) {
137
+ const isPayloadRequired = !hasEmptyPayload(eventDef);
138
+ let fn = generateEventFunction(fnName, eventDef, isPayloadRequired);
139
+
140
+ if (eventDef.$comment) {
141
+ const jsdoc = generateJSDocComment(eventDef);
142
+ fn = `${jsdoc}\n${fn}`;
143
+ }
144
+
145
+ if (eventDef.properties.event_source.const === "editor") {
146
+ editorResult.push(fn);
147
+ } else if (eventDef.properties.event_source.const === "cloudhome") {
148
+ cloudHomeResult.push(fn);
149
+ }
150
+ }
151
+
152
+ const editorFileContent = `${HEADER_LINES.join("\n")}\n\n${editorResult.join("")}`;
153
+ const cloudHomeFileContent = `${HEADER_LINES.join("\n")}\n\n${cloudHomeResult.join("")}`;
154
+
155
+ fs.writeFileSync(editorOutputPath, editorFileContent, "utf8");
156
+ fs.writeFileSync(cloudHomeOutputPath, cloudHomeFileContent, "utf8");
157
+ // eslint-disable-next-line no-console
158
+ console.log("Generated static data based on the Events JSON Schema.");