dataiku-sdk 0.1.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.
Files changed (48) hide show
  1. package/bin/dss.js +2 -0
  2. package/dist/packages/types/src/index.d.ts +458 -0
  3. package/dist/packages/types/src/index.js +384 -0
  4. package/dist/src/cli.d.ts +2 -0
  5. package/dist/src/cli.js +689 -0
  6. package/dist/src/client.d.ts +89 -0
  7. package/dist/src/client.js +301 -0
  8. package/dist/src/errors.d.ts +29 -0
  9. package/dist/src/errors.js +141 -0
  10. package/dist/src/index.d.ts +20 -0
  11. package/dist/src/index.js +24 -0
  12. package/dist/src/resources/base.d.ts +7 -0
  13. package/dist/src/resources/base.js +12 -0
  14. package/dist/src/resources/code-envs.d.ts +8 -0
  15. package/dist/src/resources/code-envs.js +36 -0
  16. package/dist/src/resources/connections.d.ts +20 -0
  17. package/dist/src/resources/connections.js +77 -0
  18. package/dist/src/resources/datasets.d.ts +57 -0
  19. package/dist/src/resources/datasets.js +423 -0
  20. package/dist/src/resources/folders.d.ts +15 -0
  21. package/dist/src/resources/folders.js +58 -0
  22. package/dist/src/resources/jobs.d.ts +72 -0
  23. package/dist/src/resources/jobs.js +184 -0
  24. package/dist/src/resources/notebooks.d.ts +34 -0
  25. package/dist/src/resources/notebooks.js +75 -0
  26. package/dist/src/resources/projects.d.ts +38 -0
  27. package/dist/src/resources/projects.js +185 -0
  28. package/dist/src/resources/recipes.d.ts +35 -0
  29. package/dist/src/resources/recipes.js +281 -0
  30. package/dist/src/resources/scenarios.d.ts +26 -0
  31. package/dist/src/resources/scenarios.js +57 -0
  32. package/dist/src/resources/sql.d.ts +40 -0
  33. package/dist/src/resources/sql.js +40 -0
  34. package/dist/src/resources/variables.d.ts +10 -0
  35. package/dist/src/resources/variables.js +22 -0
  36. package/dist/src/schemas.d.ts +7 -0
  37. package/dist/src/schemas.js +6 -0
  38. package/dist/src/utils/deep-merge.d.ts +1 -0
  39. package/dist/src/utils/deep-merge.js +15 -0
  40. package/dist/src/utils/flow-map.d.ts +37 -0
  41. package/dist/src/utils/flow-map.js +296 -0
  42. package/dist/src/utils/pagination.d.ts +8 -0
  43. package/dist/src/utils/pagination.js +23 -0
  44. package/dist/src/utils/sanitize.d.ts +2 -0
  45. package/dist/src/utils/sanitize.js +16 -0
  46. package/package.json +47 -0
  47. package/packages/types/dist/index.d.ts +458 -0
  48. package/packages/types/dist/index.js +384 -0
@@ -0,0 +1,296 @@
1
+ function asRecord(value) {
2
+ if (value && typeof value === "object" && !Array.isArray(value)) {
3
+ return value;
4
+ }
5
+ return undefined;
6
+ }
7
+ function asString(value) {
8
+ return typeof value === "string" && value.length > 0 ? value : undefined;
9
+ }
10
+ function asStringArray(value, warnings, context) {
11
+ if (value === undefined)
12
+ return [];
13
+ if (!Array.isArray(value)) {
14
+ warnings.push(`Skipped non-array "${context}" field.`);
15
+ return [];
16
+ }
17
+ const out = [];
18
+ for (const item of value) {
19
+ if (typeof item === "string" && item.length > 0)
20
+ out.push(item);
21
+ else
22
+ warnings.push(`Skipped non-string item in "${context}".`);
23
+ }
24
+ return out;
25
+ }
26
+ function inferKind(type) {
27
+ if (!type)
28
+ return "other";
29
+ const upper = type.toUpperCase();
30
+ if (upper.includes("RECIPE"))
31
+ return "recipe";
32
+ if (upper.includes("DATASET"))
33
+ return "dataset";
34
+ if (upper.includes("FOLDER"))
35
+ return "folder";
36
+ return "other";
37
+ }
38
+ function inferSubtypeFromType(type) {
39
+ if (!type)
40
+ return undefined;
41
+ const upper = type.toUpperCase();
42
+ if (upper.includes("IMPLICIT_RECIPE"))
43
+ return "implicit";
44
+ return undefined;
45
+ }
46
+ function relationRank(relation) {
47
+ switch (relation) {
48
+ case "reads":
49
+ case "writes":
50
+ return 3;
51
+ case "depends_on":
52
+ return 2;
53
+ default:
54
+ return 1;
55
+ }
56
+ }
57
+ function inferRelation(fromKind, toKind) {
58
+ if ((fromKind === "dataset" || fromKind === "folder") && toKind === "recipe") {
59
+ return "reads";
60
+ }
61
+ if (fromKind === "recipe" && (toKind === "dataset" || toKind === "folder")) {
62
+ return "writes";
63
+ }
64
+ if (fromKind === "other" || toKind === "other")
65
+ return "unknown";
66
+ return "depends_on";
67
+ }
68
+ function preferredKind(current, incoming) {
69
+ if (current === incoming)
70
+ return current;
71
+ if (current === "other")
72
+ return incoming;
73
+ return current;
74
+ }
75
+ function getConnection(record) {
76
+ const direct = asString(record.connection);
77
+ if (direct)
78
+ return direct;
79
+ const params = asRecord(record.params);
80
+ return asString(params?.connection);
81
+ }
82
+ function mergeUnique(...lists) {
83
+ const out = new Set();
84
+ for (const list of lists) {
85
+ if (!list)
86
+ continue;
87
+ for (const item of list) {
88
+ if (item)
89
+ out.add(item);
90
+ }
91
+ }
92
+ return [...out,];
93
+ }
94
+ export function normalizeFlowGraph(raw, projectKey, options = {}) {
95
+ const warnings = [];
96
+ const root = asRecord(raw);
97
+ if (!root) {
98
+ return {
99
+ projectKey,
100
+ nodes: [],
101
+ edges: [],
102
+ stats: {
103
+ nodeCount: 0,
104
+ edgeCount: 0,
105
+ datasets: 0,
106
+ recipes: 0,
107
+ roots: 0,
108
+ leaves: 0,
109
+ },
110
+ roots: [],
111
+ leaves: [],
112
+ warnings: ["Flow graph response was not an object.",],
113
+ };
114
+ }
115
+ const nodeMap = new Map();
116
+ const aliasMap = new Map();
117
+ function upsertNode(node) {
118
+ const existing = nodeMap.get(node.id);
119
+ if (!existing) {
120
+ nodeMap.set(node.id, {
121
+ ...node,
122
+ predecessors: [],
123
+ successors: [],
124
+ });
125
+ return;
126
+ }
127
+ existing.kind = preferredKind(existing.kind, node.kind);
128
+ if (!existing.name && node.name)
129
+ existing.name = node.name;
130
+ if (!existing.subtype && node.subtype)
131
+ existing.subtype = node.subtype;
132
+ if (!existing.connection && node.connection)
133
+ existing.connection = node.connection;
134
+ }
135
+ function ensureNode(id) {
136
+ const existing = nodeMap.get(id);
137
+ if (existing)
138
+ return existing;
139
+ const placeholder = {
140
+ id,
141
+ kind: "other",
142
+ name: id,
143
+ predecessors: [],
144
+ successors: [],
145
+ };
146
+ nodeMap.set(id, placeholder);
147
+ warnings.push(`Added placeholder node for "${id}" referenced by an edge.`);
148
+ return placeholder;
149
+ }
150
+ function addAlias(alias, id) {
151
+ if (!alias)
152
+ return;
153
+ aliasMap.set(alias, id);
154
+ }
155
+ const rawNodes = root.nodes;
156
+ if (rawNodes !== undefined && !asRecord(rawNodes)) {
157
+ warnings.push('Skipped "nodes" because it was not an object.');
158
+ }
159
+ for (const [nodeKey, nodeValue,] of Object.entries(asRecord(rawNodes) ?? {})) {
160
+ const nodeObj = asRecord(nodeValue);
161
+ if (!nodeObj) {
162
+ warnings.push(`Skipped node "${nodeKey}" because it was not an object.`);
163
+ continue;
164
+ }
165
+ const ref = asString(nodeObj.ref);
166
+ const id = ref ?? nodeKey;
167
+ const kind = inferKind(asString(nodeObj.type));
168
+ const subtype = asString(nodeObj.subType)
169
+ ?? asString(nodeObj.subtype)
170
+ ?? inferSubtypeFromType(asString(nodeObj.type));
171
+ const fallbackName = asString(nodeObj.name) ?? asString(nodeObj.label) ?? ref ?? nodeKey;
172
+ const folderName = kind === "folder" ? asString(options.folderNamesById?.[id]) : undefined;
173
+ const name = folderName ?? fallbackName;
174
+ const connection = getConnection(nodeObj);
175
+ upsertNode({
176
+ id,
177
+ kind,
178
+ name,
179
+ subtype,
180
+ connection,
181
+ });
182
+ addAlias(nodeKey, id);
183
+ addAlias(ref, id);
184
+ addAlias(asString(nodeObj.id), id);
185
+ const node = ensureNode(id);
186
+ node.predecessors = asStringArray(nodeObj.predecessors, warnings, `nodes.${nodeKey}.predecessors`);
187
+ node.successors = asStringArray(nodeObj.successors, warnings, `nodes.${nodeKey}.successors`);
188
+ }
189
+ const datasets = mergeUnique(asStringArray(root.datasets, warnings, "datasets"), options.allDatasetNames);
190
+ for (const dataset of datasets) {
191
+ upsertNode({ id: dataset, kind: "dataset", name: dataset, });
192
+ addAlias(dataset, dataset);
193
+ }
194
+ const recipes = mergeUnique(asStringArray(root.recipes, warnings, "recipes"), options.allRecipeNames);
195
+ for (const recipe of recipes) {
196
+ upsertNode({ id: recipe, kind: "recipe", name: recipe, });
197
+ addAlias(recipe, recipe);
198
+ }
199
+ const folders = mergeUnique(asStringArray(root.folders, warnings, "folders"), options.allFolderIds);
200
+ for (const folder of folders) {
201
+ upsertNode({
202
+ id: folder,
203
+ kind: "folder",
204
+ name: asString(options.folderNamesById?.[folder]) ?? folder,
205
+ });
206
+ addAlias(folder, folder);
207
+ }
208
+ // Best-effort rename of folder nodes using managed folder metadata lookup.
209
+ for (const [id, node,] of nodeMap.entries()) {
210
+ if (node.kind !== "folder")
211
+ continue;
212
+ const friendlyName = asString(options.folderNamesById?.[id]);
213
+ if (!friendlyName)
214
+ continue;
215
+ if (!node.name || node.name === id)
216
+ node.name = friendlyName;
217
+ }
218
+ function resolveAlias(ref) {
219
+ return aliasMap.get(ref) ?? ref;
220
+ }
221
+ const edgeMap = new Map();
222
+ function addEdge(fromRef, toRef) {
223
+ const from = resolveAlias(fromRef);
224
+ const to = resolveAlias(toRef);
225
+ const fromNode = ensureNode(from);
226
+ const toNode = ensureNode(to);
227
+ const relation = inferRelation(fromNode.kind, toNode.kind);
228
+ const key = `${from}::${to}`;
229
+ const existing = edgeMap.get(key);
230
+ if (!existing || relationRank(relation) > relationRank(existing.relation)) {
231
+ edgeMap.set(key, { from, to, relation, });
232
+ }
233
+ }
234
+ for (const node of nodeMap.values()) {
235
+ for (const predecessor of node.predecessors) {
236
+ addEdge(predecessor, node.id);
237
+ }
238
+ for (const successor of node.successors) {
239
+ addEdge(node.id, successor);
240
+ }
241
+ }
242
+ const nodes = [...nodeMap.values(),]
243
+ .map((node) => ({
244
+ id: node.id,
245
+ kind: node.kind,
246
+ name: node.name,
247
+ subtype: node.subtype,
248
+ connection: node.connection,
249
+ }))
250
+ .sort((a, b) => a.id.localeCompare(b.id));
251
+ const edges = [...edgeMap.values(),].sort((a, b) => {
252
+ const byFrom = a.from.localeCompare(b.from);
253
+ if (byFrom !== 0)
254
+ return byFrom;
255
+ const byTo = a.to.localeCompare(b.to);
256
+ if (byTo !== 0)
257
+ return byTo;
258
+ return a.relation.localeCompare(b.relation);
259
+ });
260
+ const inDegree = new Map();
261
+ const outDegree = new Map();
262
+ for (const node of nodes) {
263
+ inDegree.set(node.id, 0);
264
+ outDegree.set(node.id, 0);
265
+ }
266
+ for (const edge of edges) {
267
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
268
+ outDegree.set(edge.from, (outDegree.get(edge.from) ?? 0) + 1);
269
+ }
270
+ const roots = nodes
271
+ .filter((node) => (inDegree.get(node.id) ?? 0) === 0)
272
+ .map((node) => node.id)
273
+ .sort((a, b) => a.localeCompare(b));
274
+ const leaves = nodes
275
+ .filter((node) => (outDegree.get(node.id) ?? 0) === 0)
276
+ .map((node) => node.id)
277
+ .sort((a, b) => a.localeCompare(b));
278
+ const datasetsCount = nodes.filter((node) => node.kind === "dataset").length;
279
+ const recipesCount = nodes.filter((node) => node.kind === "recipe").length;
280
+ return {
281
+ projectKey,
282
+ nodes,
283
+ edges,
284
+ stats: {
285
+ nodeCount: nodes.length,
286
+ edgeCount: edges.length,
287
+ datasets: datasetsCount,
288
+ recipes: recipesCount,
289
+ roots: roots.length,
290
+ leaves: leaves.length,
291
+ },
292
+ roots,
293
+ leaves,
294
+ warnings,
295
+ };
296
+ }
@@ -0,0 +1,8 @@
1
+ export declare function normalizeQuery(query: string | undefined): string | undefined;
2
+ export declare function filterByQuery<T>(items: T[], query: string | undefined, pickValues: (item: T) => Array<string | undefined>): T[];
3
+ export declare function paginateItems<T>(items: T[], limit: number | undefined, offset: number | undefined, defaultLimit?: number): {
4
+ items: T[];
5
+ offset: number;
6
+ limit: number;
7
+ hasMore: boolean;
8
+ };
@@ -0,0 +1,23 @@
1
+ export function normalizeQuery(query) {
2
+ const normalized = query?.trim().toLowerCase();
3
+ return normalized && normalized.length > 0 ? normalized : undefined;
4
+ }
5
+ export function filterByQuery(items, query, pickValues) {
6
+ const normalized = normalizeQuery(query);
7
+ if (!normalized)
8
+ return items;
9
+ return items.filter((item) => pickValues(item).some((value) => (value ?? "").toLowerCase().includes(normalized)));
10
+ }
11
+ const DEFAULT_PAGE_LIMIT = 100;
12
+ export function paginateItems(items, limit, offset, defaultLimit = DEFAULT_PAGE_LIMIT) {
13
+ const pageOffset = Math.max(0, offset ?? 0);
14
+ const effectiveLimit = Math.max(1, limit ?? defaultLimit);
15
+ const pagedItems = items.slice(pageOffset, pageOffset + effectiveLimit);
16
+ const hasMore = pageOffset + pagedItems.length < items.length;
17
+ return {
18
+ items: pagedItems,
19
+ offset: pageOffset,
20
+ limit: effectiveLimit,
21
+ hasMore,
22
+ };
23
+ }
@@ -0,0 +1,2 @@
1
+ /** Sanitize a string for use as a local filename. */
2
+ export declare function sanitizeFileName(name: string, fallback: string): string;
@@ -0,0 +1,16 @@
1
+ const WINDOWS_RESERVED_FILE_NAMES = /^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])$/i;
2
+ /** Sanitize a string for use as a local filename. */
3
+ export function sanitizeFileName(name, fallback) {
4
+ const sanitized = name
5
+ .replace(/[<>:"/\\|?*\u0000-\u001F]/g, "_")
6
+ .replace(/[. ]+$/g, "")
7
+ .trim();
8
+ if (!sanitized)
9
+ return fallback;
10
+ const dotIndex = sanitized.indexOf(".");
11
+ const baseName = dotIndex === -1 ? sanitized : sanitized.slice(0, dotIndex);
12
+ const extension = dotIndex === -1 ? "" : sanitized.slice(dotIndex);
13
+ if (WINDOWS_RESERVED_FILE_NAMES.test(baseName))
14
+ return `${baseName}_${extension}`;
15
+ return sanitized;
16
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "dataiku-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Dataiku DSS SDK and CLI for programmatic access to DSS REST APIs",
5
+ "type": "module",
6
+ "workspaces": ["packages/*"],
7
+ "main": "dist/src/index.js",
8
+ "types": "dist/src/index.d.ts",
9
+ "bin": {
10
+ "dss": "bin/dss.js"
11
+ },
12
+ "files": [
13
+ "dist/",
14
+ "bin/dss.js",
15
+ "packages/types/dist/"
16
+ ],
17
+ "engines": {
18
+ "bun": ">=1.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc && cd packages/types && npx tsc",
22
+ "prepublishOnly": "bun run build",
23
+ "check": "tsc --noEmit",
24
+ "lint": "oxlint src/",
25
+ "lint:fix": "oxlint --fix src/",
26
+ "format": "dprint fmt",
27
+ "format:check": "dprint check",
28
+ "test": "bun test"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "^1.2.14",
32
+ "dprint": "^0.53.0",
33
+ "oxlint": "^1.55.0",
34
+ "typescript": "^5.8.0"
35
+ },
36
+ "dependencies": {
37
+ "@sinclair/typebox": "^0.34.48"
38
+ },
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/clssck/dataiku-sdk.git"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }