@synode/adapter-file 1.0.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/LICENSE +35 -0
- package/dist/index.cjs +166 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.mts +74 -0
- package/dist/index.mjs +137 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Synode Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Digitl Cloud GmbH. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person or organization
|
|
6
|
+
obtaining a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to use the Software for personal, internal, and commercial
|
|
8
|
+
purposes, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
1. PERMITTED USE. You may use, copy, and modify the Software for your own
|
|
11
|
+
personal, internal, or commercial purposes.
|
|
12
|
+
|
|
13
|
+
2. NO REDISTRIBUTION. You may not distribute, publish, sublicense, or
|
|
14
|
+
otherwise make the Software or any derivative works available to third
|
|
15
|
+
parties, whether in source code or compiled form, free of charge or for
|
|
16
|
+
a fee.
|
|
17
|
+
|
|
18
|
+
3. NO RESALE. You may not sell, rent, lease, or otherwise commercially
|
|
19
|
+
exploit the Software itself as a standalone product or as part of a
|
|
20
|
+
software distribution.
|
|
21
|
+
|
|
22
|
+
4. NO HOSTING AS A SERVICE. You may not offer the Software to third parties
|
|
23
|
+
as a hosted, managed, or software-as-a-service product where the primary
|
|
24
|
+
value derives from the Software.
|
|
25
|
+
|
|
26
|
+
5. ATTRIBUTION. You must retain this license notice and copyright notice in
|
|
27
|
+
all copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
6. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
30
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
31
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
32
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
33
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
34
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
35
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let node_fs_promises = require("node:fs/promises");
|
|
29
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
30
|
+
let node_path = require("node:path");
|
|
31
|
+
node_path = __toESM(node_path);
|
|
32
|
+
let __synode_core = require("@synode/core");
|
|
33
|
+
|
|
34
|
+
//#region src/index.ts
|
|
35
|
+
const FORMAT_EXTENSION = {
|
|
36
|
+
jsonl: "jsonl",
|
|
37
|
+
json: "json",
|
|
38
|
+
csv: "csv"
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Adapter that writes events to the local filesystem.
|
|
42
|
+
*
|
|
43
|
+
* Supports JSONL (append per event), JSON (buffered array), and CSV formats
|
|
44
|
+
* with optional daily partitioning by event timestamp.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });
|
|
49
|
+
* await generate(journey, { users: 10, adapter });
|
|
50
|
+
* await adapter.close();
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* // Daily-partitioned CSV output
|
|
56
|
+
* const adapter = new FileAdapter({
|
|
57
|
+
* path: './out',
|
|
58
|
+
* format: 'csv',
|
|
59
|
+
* partition: 'daily',
|
|
60
|
+
* filePattern: 'events-{date}.{ext}',
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
var FileAdapter = class {
|
|
65
|
+
options;
|
|
66
|
+
jsonBuffer = [];
|
|
67
|
+
csvHeadersWritten = /* @__PURE__ */ new Set();
|
|
68
|
+
csvHeaders = null;
|
|
69
|
+
constructor(options) {
|
|
70
|
+
this.options = {
|
|
71
|
+
partition: "none",
|
|
72
|
+
filePattern: "events-{date}.{ext}",
|
|
73
|
+
...options
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** @inheritdoc */
|
|
77
|
+
async write(event) {
|
|
78
|
+
const filePath = this.resolveFilePath(event);
|
|
79
|
+
await this.ensureDirectory(filePath);
|
|
80
|
+
switch (this.options.format) {
|
|
81
|
+
case "jsonl":
|
|
82
|
+
await this.writeJsonl(filePath, event);
|
|
83
|
+
break;
|
|
84
|
+
case "json":
|
|
85
|
+
this.jsonBuffer.push(event);
|
|
86
|
+
break;
|
|
87
|
+
case "csv":
|
|
88
|
+
await this.writeCsv(filePath, event);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** @inheritdoc */
|
|
93
|
+
async close() {
|
|
94
|
+
if (this.options.format !== "json") return;
|
|
95
|
+
if (this.options.partition === "daily") {
|
|
96
|
+
const grouped = this.groupByDate(this.jsonBuffer);
|
|
97
|
+
for (const [date, events] of grouped) {
|
|
98
|
+
const filePath = this.buildPartitionedPath(date);
|
|
99
|
+
await this.ensureDirectory(filePath);
|
|
100
|
+
await node_fs_promises.writeFile(filePath, JSON.stringify(events, null, 2), "utf-8");
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const filePath = this.safePath(this.options.path);
|
|
104
|
+
await this.ensureDirectory(filePath);
|
|
105
|
+
await node_fs_promises.writeFile(filePath, JSON.stringify(this.jsonBuffer, null, 2), "utf-8");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
resolveFilePath(event) {
|
|
109
|
+
if (this.options.partition === "daily") {
|
|
110
|
+
const date = this.formatDate(event.timestamp);
|
|
111
|
+
return this.buildPartitionedPath(date);
|
|
112
|
+
}
|
|
113
|
+
return this.safePath(this.options.path);
|
|
114
|
+
}
|
|
115
|
+
buildPartitionedPath(date) {
|
|
116
|
+
const ext = FORMAT_EXTENSION[this.options.format];
|
|
117
|
+
return (0, __synode_core.validateFilePath)(this.options.filePattern.replace("{date}", date).replace("{ext}", ext), node_path.resolve(this.options.path));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolves and validates a file path against its own parent directory.
|
|
121
|
+
* This prevents path traversal (e.g., `../../etc/passwd`) while allowing
|
|
122
|
+
* absolute paths outside the current working directory.
|
|
123
|
+
*/
|
|
124
|
+
safePath(filePath) {
|
|
125
|
+
const resolved = node_path.resolve(filePath);
|
|
126
|
+
return (0, __synode_core.validateFilePath)(resolved, node_path.dirname(resolved));
|
|
127
|
+
}
|
|
128
|
+
async ensureDirectory(filePath) {
|
|
129
|
+
await node_fs_promises.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
async writeJsonl(filePath, event) {
|
|
132
|
+
await node_fs_promises.appendFile(filePath, JSON.stringify(event) + "\n", "utf-8");
|
|
133
|
+
}
|
|
134
|
+
async writeCsv(filePath, event) {
|
|
135
|
+
this.csvHeaders ??= Object.keys(event.payload);
|
|
136
|
+
if (!this.csvHeadersWritten.has(filePath)) {
|
|
137
|
+
await node_fs_promises.appendFile(filePath, this.csvHeaders.join(",") + "\n", "utf-8");
|
|
138
|
+
this.csvHeadersWritten.add(filePath);
|
|
139
|
+
}
|
|
140
|
+
const row = this.csvHeaders.map((header) => this.escapeCSVValue(event.payload[header]));
|
|
141
|
+
await node_fs_promises.appendFile(filePath, row.join(",") + "\n", "utf-8");
|
|
142
|
+
}
|
|
143
|
+
escapeCSVValue(value) {
|
|
144
|
+
if (value == null) return "";
|
|
145
|
+
const str = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? value.toString() : JSON.stringify(value);
|
|
146
|
+
if (str.includes(",") || str.includes("\"") || str.includes("\n")) return "\"" + str.replace(/"/g, "\"\"") + "\"";
|
|
147
|
+
return str;
|
|
148
|
+
}
|
|
149
|
+
formatDate(date) {
|
|
150
|
+
return `${String(date.getUTCFullYear())}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
151
|
+
}
|
|
152
|
+
groupByDate(events) {
|
|
153
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const event of events) {
|
|
155
|
+
const date = this.formatDate(event.timestamp);
|
|
156
|
+
const existing = grouped.get(date);
|
|
157
|
+
if (existing) existing.push(event);
|
|
158
|
+
else grouped.set(date, [event]);
|
|
159
|
+
}
|
|
160
|
+
return grouped;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
exports.FileAdapter = FileAdapter;
|
|
166
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["FORMAT_EXTENSION: Record<FileAdapterOptions['format'], string>","fs","path"],"sources":["../src/index.ts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Event, OutputAdapter } from '@synode/core';\nimport { validateFilePath } from '@synode/core';\n\n/**\n * Configuration options for {@link FileAdapter}.\n *\n * @param path - Output file path (used directly when partition is 'none')\n * @param format - Serialization format: 'jsonl' (one JSON object per line),\n * 'json' (single JSON array), or 'csv' (header + rows)\n * @param partition - Partitioning strategy. 'daily' creates separate files\n * based on each event's timestamp date. Defaults to 'none'.\n * @param filePattern - Template for partitioned file names. Supports `{date}`\n * (replaced with YYYY-MM-DD) and `{ext}` (replaced with the format extension).\n * Defaults to `'events-{date}.{ext}'`.\n */\nexport interface FileAdapterOptions {\n path: string;\n format: 'jsonl' | 'json' | 'csv';\n partition?: 'daily' | 'none';\n filePattern?: string;\n}\n\nconst FORMAT_EXTENSION: Record<FileAdapterOptions['format'], string> = {\n jsonl: 'jsonl',\n json: 'json',\n csv: 'csv',\n};\n\n/**\n * Adapter that writes events to the local filesystem.\n *\n * Supports JSONL (append per event), JSON (buffered array), and CSV formats\n * with optional daily partitioning by event timestamp.\n *\n * @example\n * ```ts\n * const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });\n * await generate(journey, { users: 10, adapter });\n * await adapter.close();\n * ```\n *\n * @example\n * ```ts\n * // Daily-partitioned CSV output\n * const adapter = new FileAdapter({\n * path: './out',\n * format: 'csv',\n * partition: 'daily',\n * filePattern: 'events-{date}.{ext}',\n * });\n * ```\n */\nexport class FileAdapter implements OutputAdapter {\n private readonly options: Required<FileAdapterOptions>;\n private readonly jsonBuffer: Event[] = [];\n private readonly csvHeadersWritten = new Set<string>();\n private csvHeaders: string[] | null = null;\n\n constructor(options: FileAdapterOptions) {\n this.options = {\n partition: 'none',\n filePattern: 'events-{date}.{ext}',\n ...options,\n };\n }\n\n /** @inheritdoc */\n async write(event: Event): Promise<void> {\n const filePath = this.resolveFilePath(event);\n await this.ensureDirectory(filePath);\n\n switch (this.options.format) {\n case 'jsonl':\n await this.writeJsonl(filePath, event);\n break;\n case 'json':\n this.jsonBuffer.push(event);\n break;\n case 'csv':\n await this.writeCsv(filePath, event);\n break;\n }\n }\n\n /** @inheritdoc */\n async close(): Promise<void> {\n if (this.options.format !== 'json') {\n return;\n }\n\n if (this.options.partition === 'daily') {\n const grouped = this.groupByDate(this.jsonBuffer);\n for (const [date, events] of grouped) {\n const filePath = this.buildPartitionedPath(date);\n await this.ensureDirectory(filePath);\n await fs.writeFile(filePath, JSON.stringify(events, null, 2), 'utf-8');\n }\n } else {\n const filePath = this.safePath(this.options.path);\n await this.ensureDirectory(filePath);\n await fs.writeFile(filePath, JSON.stringify(this.jsonBuffer, null, 2), 'utf-8');\n }\n }\n\n private resolveFilePath(event: Event): string {\n if (this.options.partition === 'daily') {\n const date = this.formatDate(event.timestamp);\n return this.buildPartitionedPath(date);\n }\n return this.safePath(this.options.path);\n }\n\n private buildPartitionedPath(date: string): string {\n const ext = FORMAT_EXTENSION[this.options.format];\n const fileName = this.options.filePattern.replace('{date}', date).replace('{ext}', ext);\n const baseDir = path.resolve(this.options.path);\n return validateFilePath(fileName, baseDir);\n }\n\n /**\n * Resolves and validates a file path against its own parent directory.\n * This prevents path traversal (e.g., `../../etc/passwd`) while allowing\n * absolute paths outside the current working directory.\n */\n private safePath(filePath: string): string {\n const resolved = path.resolve(filePath);\n const parentDir = path.dirname(resolved);\n return validateFilePath(resolved, parentDir);\n }\n\n private async ensureDirectory(filePath: string): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n }\n\n private async writeJsonl(filePath: string, event: Event): Promise<void> {\n await fs.appendFile(filePath, JSON.stringify(event) + '\\n', 'utf-8');\n }\n\n private async writeCsv(filePath: string, event: Event): Promise<void> {\n this.csvHeaders ??= Object.keys(event.payload);\n\n if (!this.csvHeadersWritten.has(filePath)) {\n await fs.appendFile(filePath, this.csvHeaders.join(',') + '\\n', 'utf-8');\n this.csvHeadersWritten.add(filePath);\n }\n\n const row = this.csvHeaders.map((header) => this.escapeCSVValue(event.payload[header]));\n await fs.appendFile(filePath, row.join(',') + '\\n', 'utf-8');\n }\n\n private escapeCSVValue(value: unknown): string {\n if (value == null) {\n return '';\n }\n const str =\n typeof value === 'string'\n ? value\n : typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint'\n ? value.toString()\n : JSON.stringify(value);\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n }\n return str;\n }\n\n private formatDate(date: Date): string {\n const year = String(date.getUTCFullYear());\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n }\n\n private groupByDate(events: Event[]): Map<string, Event[]> {\n const grouped = new Map<string, Event[]>();\n for (const event of events) {\n const date = this.formatDate(event.timestamp);\n const existing = grouped.get(date);\n if (existing) {\n existing.push(event);\n } else {\n grouped.set(date, [event]);\n }\n }\n return grouped;\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Event, OutputAdapter } from "@synode/core";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for {@link FileAdapter}.
|
|
7
|
+
*
|
|
8
|
+
* @param path - Output file path (used directly when partition is 'none')
|
|
9
|
+
* @param format - Serialization format: 'jsonl' (one JSON object per line),
|
|
10
|
+
* 'json' (single JSON array), or 'csv' (header + rows)
|
|
11
|
+
* @param partition - Partitioning strategy. 'daily' creates separate files
|
|
12
|
+
* based on each event's timestamp date. Defaults to 'none'.
|
|
13
|
+
* @param filePattern - Template for partitioned file names. Supports `{date}`
|
|
14
|
+
* (replaced with YYYY-MM-DD) and `{ext}` (replaced with the format extension).
|
|
15
|
+
* Defaults to `'events-{date}.{ext}'`.
|
|
16
|
+
*/
|
|
17
|
+
interface FileAdapterOptions {
|
|
18
|
+
path: string;
|
|
19
|
+
format: 'jsonl' | 'json' | 'csv';
|
|
20
|
+
partition?: 'daily' | 'none';
|
|
21
|
+
filePattern?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adapter that writes events to the local filesystem.
|
|
25
|
+
*
|
|
26
|
+
* Supports JSONL (append per event), JSON (buffered array), and CSV formats
|
|
27
|
+
* with optional daily partitioning by event timestamp.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });
|
|
32
|
+
* await generate(journey, { users: 10, adapter });
|
|
33
|
+
* await adapter.close();
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* // Daily-partitioned CSV output
|
|
39
|
+
* const adapter = new FileAdapter({
|
|
40
|
+
* path: './out',
|
|
41
|
+
* format: 'csv',
|
|
42
|
+
* partition: 'daily',
|
|
43
|
+
* filePattern: 'events-{date}.{ext}',
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class FileAdapter implements OutputAdapter {
|
|
48
|
+
private readonly options;
|
|
49
|
+
private readonly jsonBuffer;
|
|
50
|
+
private readonly csvHeadersWritten;
|
|
51
|
+
private csvHeaders;
|
|
52
|
+
constructor(options: FileAdapterOptions);
|
|
53
|
+
/** @inheritdoc */
|
|
54
|
+
write(event: Event): Promise<void>;
|
|
55
|
+
/** @inheritdoc */
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
private resolveFilePath;
|
|
58
|
+
private buildPartitionedPath;
|
|
59
|
+
/**
|
|
60
|
+
* Resolves and validates a file path against its own parent directory.
|
|
61
|
+
* This prevents path traversal (e.g., `../../etc/passwd`) while allowing
|
|
62
|
+
* absolute paths outside the current working directory.
|
|
63
|
+
*/
|
|
64
|
+
private safePath;
|
|
65
|
+
private ensureDirectory;
|
|
66
|
+
private writeJsonl;
|
|
67
|
+
private writeCsv;
|
|
68
|
+
private escapeCSVValue;
|
|
69
|
+
private formatDate;
|
|
70
|
+
private groupByDate;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { FileAdapter, FileAdapterOptions };
|
|
74
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Event, OutputAdapter } from "@synode/core";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for {@link FileAdapter}.
|
|
7
|
+
*
|
|
8
|
+
* @param path - Output file path (used directly when partition is 'none')
|
|
9
|
+
* @param format - Serialization format: 'jsonl' (one JSON object per line),
|
|
10
|
+
* 'json' (single JSON array), or 'csv' (header + rows)
|
|
11
|
+
* @param partition - Partitioning strategy. 'daily' creates separate files
|
|
12
|
+
* based on each event's timestamp date. Defaults to 'none'.
|
|
13
|
+
* @param filePattern - Template for partitioned file names. Supports `{date}`
|
|
14
|
+
* (replaced with YYYY-MM-DD) and `{ext}` (replaced with the format extension).
|
|
15
|
+
* Defaults to `'events-{date}.{ext}'`.
|
|
16
|
+
*/
|
|
17
|
+
interface FileAdapterOptions {
|
|
18
|
+
path: string;
|
|
19
|
+
format: 'jsonl' | 'json' | 'csv';
|
|
20
|
+
partition?: 'daily' | 'none';
|
|
21
|
+
filePattern?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adapter that writes events to the local filesystem.
|
|
25
|
+
*
|
|
26
|
+
* Supports JSONL (append per event), JSON (buffered array), and CSV formats
|
|
27
|
+
* with optional daily partitioning by event timestamp.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });
|
|
32
|
+
* await generate(journey, { users: 10, adapter });
|
|
33
|
+
* await adapter.close();
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* // Daily-partitioned CSV output
|
|
39
|
+
* const adapter = new FileAdapter({
|
|
40
|
+
* path: './out',
|
|
41
|
+
* format: 'csv',
|
|
42
|
+
* partition: 'daily',
|
|
43
|
+
* filePattern: 'events-{date}.{ext}',
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class FileAdapter implements OutputAdapter {
|
|
48
|
+
private readonly options;
|
|
49
|
+
private readonly jsonBuffer;
|
|
50
|
+
private readonly csvHeadersWritten;
|
|
51
|
+
private csvHeaders;
|
|
52
|
+
constructor(options: FileAdapterOptions);
|
|
53
|
+
/** @inheritdoc */
|
|
54
|
+
write(event: Event): Promise<void>;
|
|
55
|
+
/** @inheritdoc */
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
private resolveFilePath;
|
|
58
|
+
private buildPartitionedPath;
|
|
59
|
+
/**
|
|
60
|
+
* Resolves and validates a file path against its own parent directory.
|
|
61
|
+
* This prevents path traversal (e.g., `../../etc/passwd`) while allowing
|
|
62
|
+
* absolute paths outside the current working directory.
|
|
63
|
+
*/
|
|
64
|
+
private safePath;
|
|
65
|
+
private ensureDirectory;
|
|
66
|
+
private writeJsonl;
|
|
67
|
+
private writeCsv;
|
|
68
|
+
private escapeCSVValue;
|
|
69
|
+
private formatDate;
|
|
70
|
+
private groupByDate;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { FileAdapter, FileAdapterOptions };
|
|
74
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { validateFilePath } from "@synode/core";
|
|
4
|
+
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
const FORMAT_EXTENSION = {
|
|
7
|
+
jsonl: "jsonl",
|
|
8
|
+
json: "json",
|
|
9
|
+
csv: "csv"
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Adapter that writes events to the local filesystem.
|
|
13
|
+
*
|
|
14
|
+
* Supports JSONL (append per event), JSON (buffered array), and CSV formats
|
|
15
|
+
* with optional daily partitioning by event timestamp.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });
|
|
20
|
+
* await generate(journey, { users: 10, adapter });
|
|
21
|
+
* await adapter.close();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Daily-partitioned CSV output
|
|
27
|
+
* const adapter = new FileAdapter({
|
|
28
|
+
* path: './out',
|
|
29
|
+
* format: 'csv',
|
|
30
|
+
* partition: 'daily',
|
|
31
|
+
* filePattern: 'events-{date}.{ext}',
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
var FileAdapter = class {
|
|
36
|
+
options;
|
|
37
|
+
jsonBuffer = [];
|
|
38
|
+
csvHeadersWritten = /* @__PURE__ */ new Set();
|
|
39
|
+
csvHeaders = null;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = {
|
|
42
|
+
partition: "none",
|
|
43
|
+
filePattern: "events-{date}.{ext}",
|
|
44
|
+
...options
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** @inheritdoc */
|
|
48
|
+
async write(event) {
|
|
49
|
+
const filePath = this.resolveFilePath(event);
|
|
50
|
+
await this.ensureDirectory(filePath);
|
|
51
|
+
switch (this.options.format) {
|
|
52
|
+
case "jsonl":
|
|
53
|
+
await this.writeJsonl(filePath, event);
|
|
54
|
+
break;
|
|
55
|
+
case "json":
|
|
56
|
+
this.jsonBuffer.push(event);
|
|
57
|
+
break;
|
|
58
|
+
case "csv":
|
|
59
|
+
await this.writeCsv(filePath, event);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** @inheritdoc */
|
|
64
|
+
async close() {
|
|
65
|
+
if (this.options.format !== "json") return;
|
|
66
|
+
if (this.options.partition === "daily") {
|
|
67
|
+
const grouped = this.groupByDate(this.jsonBuffer);
|
|
68
|
+
for (const [date, events] of grouped) {
|
|
69
|
+
const filePath = this.buildPartitionedPath(date);
|
|
70
|
+
await this.ensureDirectory(filePath);
|
|
71
|
+
await fs.writeFile(filePath, JSON.stringify(events, null, 2), "utf-8");
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const filePath = this.safePath(this.options.path);
|
|
75
|
+
await this.ensureDirectory(filePath);
|
|
76
|
+
await fs.writeFile(filePath, JSON.stringify(this.jsonBuffer, null, 2), "utf-8");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
resolveFilePath(event) {
|
|
80
|
+
if (this.options.partition === "daily") {
|
|
81
|
+
const date = this.formatDate(event.timestamp);
|
|
82
|
+
return this.buildPartitionedPath(date);
|
|
83
|
+
}
|
|
84
|
+
return this.safePath(this.options.path);
|
|
85
|
+
}
|
|
86
|
+
buildPartitionedPath(date) {
|
|
87
|
+
const ext = FORMAT_EXTENSION[this.options.format];
|
|
88
|
+
return validateFilePath(this.options.filePattern.replace("{date}", date).replace("{ext}", ext), path.resolve(this.options.path));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolves and validates a file path against its own parent directory.
|
|
92
|
+
* This prevents path traversal (e.g., `../../etc/passwd`) while allowing
|
|
93
|
+
* absolute paths outside the current working directory.
|
|
94
|
+
*/
|
|
95
|
+
safePath(filePath) {
|
|
96
|
+
const resolved = path.resolve(filePath);
|
|
97
|
+
return validateFilePath(resolved, path.dirname(resolved));
|
|
98
|
+
}
|
|
99
|
+
async ensureDirectory(filePath) {
|
|
100
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
async writeJsonl(filePath, event) {
|
|
103
|
+
await fs.appendFile(filePath, JSON.stringify(event) + "\n", "utf-8");
|
|
104
|
+
}
|
|
105
|
+
async writeCsv(filePath, event) {
|
|
106
|
+
this.csvHeaders ??= Object.keys(event.payload);
|
|
107
|
+
if (!this.csvHeadersWritten.has(filePath)) {
|
|
108
|
+
await fs.appendFile(filePath, this.csvHeaders.join(",") + "\n", "utf-8");
|
|
109
|
+
this.csvHeadersWritten.add(filePath);
|
|
110
|
+
}
|
|
111
|
+
const row = this.csvHeaders.map((header) => this.escapeCSVValue(event.payload[header]));
|
|
112
|
+
await fs.appendFile(filePath, row.join(",") + "\n", "utf-8");
|
|
113
|
+
}
|
|
114
|
+
escapeCSVValue(value) {
|
|
115
|
+
if (value == null) return "";
|
|
116
|
+
const str = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? value.toString() : JSON.stringify(value);
|
|
117
|
+
if (str.includes(",") || str.includes("\"") || str.includes("\n")) return "\"" + str.replace(/"/g, "\"\"") + "\"";
|
|
118
|
+
return str;
|
|
119
|
+
}
|
|
120
|
+
formatDate(date) {
|
|
121
|
+
return `${String(date.getUTCFullYear())}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
122
|
+
}
|
|
123
|
+
groupByDate(events) {
|
|
124
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const event of events) {
|
|
126
|
+
const date = this.formatDate(event.timestamp);
|
|
127
|
+
const existing = grouped.get(date);
|
|
128
|
+
if (existing) existing.push(event);
|
|
129
|
+
else grouped.set(date, [event]);
|
|
130
|
+
}
|
|
131
|
+
return grouped;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
export { FileAdapter };
|
|
137
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["FORMAT_EXTENSION: Record<FileAdapterOptions['format'], string>"],"sources":["../src/index.ts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Event, OutputAdapter } from '@synode/core';\nimport { validateFilePath } from '@synode/core';\n\n/**\n * Configuration options for {@link FileAdapter}.\n *\n * @param path - Output file path (used directly when partition is 'none')\n * @param format - Serialization format: 'jsonl' (one JSON object per line),\n * 'json' (single JSON array), or 'csv' (header + rows)\n * @param partition - Partitioning strategy. 'daily' creates separate files\n * based on each event's timestamp date. Defaults to 'none'.\n * @param filePattern - Template for partitioned file names. Supports `{date}`\n * (replaced with YYYY-MM-DD) and `{ext}` (replaced with the format extension).\n * Defaults to `'events-{date}.{ext}'`.\n */\nexport interface FileAdapterOptions {\n path: string;\n format: 'jsonl' | 'json' | 'csv';\n partition?: 'daily' | 'none';\n filePattern?: string;\n}\n\nconst FORMAT_EXTENSION: Record<FileAdapterOptions['format'], string> = {\n jsonl: 'jsonl',\n json: 'json',\n csv: 'csv',\n};\n\n/**\n * Adapter that writes events to the local filesystem.\n *\n * Supports JSONL (append per event), JSON (buffered array), and CSV formats\n * with optional daily partitioning by event timestamp.\n *\n * @example\n * ```ts\n * const adapter = new FileAdapter({ path: './out/events.jsonl', format: 'jsonl' });\n * await generate(journey, { users: 10, adapter });\n * await adapter.close();\n * ```\n *\n * @example\n * ```ts\n * // Daily-partitioned CSV output\n * const adapter = new FileAdapter({\n * path: './out',\n * format: 'csv',\n * partition: 'daily',\n * filePattern: 'events-{date}.{ext}',\n * });\n * ```\n */\nexport class FileAdapter implements OutputAdapter {\n private readonly options: Required<FileAdapterOptions>;\n private readonly jsonBuffer: Event[] = [];\n private readonly csvHeadersWritten = new Set<string>();\n private csvHeaders: string[] | null = null;\n\n constructor(options: FileAdapterOptions) {\n this.options = {\n partition: 'none',\n filePattern: 'events-{date}.{ext}',\n ...options,\n };\n }\n\n /** @inheritdoc */\n async write(event: Event): Promise<void> {\n const filePath = this.resolveFilePath(event);\n await this.ensureDirectory(filePath);\n\n switch (this.options.format) {\n case 'jsonl':\n await this.writeJsonl(filePath, event);\n break;\n case 'json':\n this.jsonBuffer.push(event);\n break;\n case 'csv':\n await this.writeCsv(filePath, event);\n break;\n }\n }\n\n /** @inheritdoc */\n async close(): Promise<void> {\n if (this.options.format !== 'json') {\n return;\n }\n\n if (this.options.partition === 'daily') {\n const grouped = this.groupByDate(this.jsonBuffer);\n for (const [date, events] of grouped) {\n const filePath = this.buildPartitionedPath(date);\n await this.ensureDirectory(filePath);\n await fs.writeFile(filePath, JSON.stringify(events, null, 2), 'utf-8');\n }\n } else {\n const filePath = this.safePath(this.options.path);\n await this.ensureDirectory(filePath);\n await fs.writeFile(filePath, JSON.stringify(this.jsonBuffer, null, 2), 'utf-8');\n }\n }\n\n private resolveFilePath(event: Event): string {\n if (this.options.partition === 'daily') {\n const date = this.formatDate(event.timestamp);\n return this.buildPartitionedPath(date);\n }\n return this.safePath(this.options.path);\n }\n\n private buildPartitionedPath(date: string): string {\n const ext = FORMAT_EXTENSION[this.options.format];\n const fileName = this.options.filePattern.replace('{date}', date).replace('{ext}', ext);\n const baseDir = path.resolve(this.options.path);\n return validateFilePath(fileName, baseDir);\n }\n\n /**\n * Resolves and validates a file path against its own parent directory.\n * This prevents path traversal (e.g., `../../etc/passwd`) while allowing\n * absolute paths outside the current working directory.\n */\n private safePath(filePath: string): string {\n const resolved = path.resolve(filePath);\n const parentDir = path.dirname(resolved);\n return validateFilePath(resolved, parentDir);\n }\n\n private async ensureDirectory(filePath: string): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n }\n\n private async writeJsonl(filePath: string, event: Event): Promise<void> {\n await fs.appendFile(filePath, JSON.stringify(event) + '\\n', 'utf-8');\n }\n\n private async writeCsv(filePath: string, event: Event): Promise<void> {\n this.csvHeaders ??= Object.keys(event.payload);\n\n if (!this.csvHeadersWritten.has(filePath)) {\n await fs.appendFile(filePath, this.csvHeaders.join(',') + '\\n', 'utf-8');\n this.csvHeadersWritten.add(filePath);\n }\n\n const row = this.csvHeaders.map((header) => this.escapeCSVValue(event.payload[header]));\n await fs.appendFile(filePath, row.join(',') + '\\n', 'utf-8');\n }\n\n private escapeCSVValue(value: unknown): string {\n if (value == null) {\n return '';\n }\n const str =\n typeof value === 'string'\n ? value\n : typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint'\n ? value.toString()\n : JSON.stringify(value);\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n }\n return str;\n }\n\n private formatDate(date: Date): string {\n const year = String(date.getUTCFullYear());\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n }\n\n private groupByDate(events: Event[]): Map<string, Event[]> {\n const grouped = new Map<string, Event[]>();\n for (const event of events) {\n const date = this.formatDate(event.timestamp);\n const existing = grouped.get(date);\n if (existing) {\n existing.push(event);\n } else {\n grouped.set(date, [event]);\n }\n }\n return grouped;\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@synode/adapter-file",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "File adapter for Synode",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"author": "Digitl Cloud GmbH",
|
|
20
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/digitl-cloud/synode",
|
|
24
|
+
"directory": "packages/adapter-file"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@synode/core": "^1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^24.12.0",
|
|
31
|
+
"eslint": "^9.39.4",
|
|
32
|
+
"eslint-config-prettier": "^10.1.8",
|
|
33
|
+
"typescript-eslint": "^8.58.0",
|
|
34
|
+
"tsdown": "^0.16.8",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vitest": "^4.1.2",
|
|
37
|
+
"@synode/core": "1.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"lint": "eslint src tests"
|
|
43
|
+
}
|
|
44
|
+
}
|