studiograph 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/README.md +18 -0
- package/dist/agent/orchestrator.d.ts +69 -0
- package/dist/agent/orchestrator.js +211 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/tools/graph-tools.d.ts +30 -0
- package/dist/agent/tools/graph-tools.js +536 -0
- package/dist/agent/tools/graph-tools.js.map +1 -0
- package/dist/auth/github.d.ts +53 -0
- package/dist/auth/github.js +180 -0
- package/dist/auth/github.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.js +63 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +299 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/join.d.ts +14 -0
- package/dist/cli/commands/join.js +230 -0
- package/dist/cli/commands/join.js.map +1 -0
- package/dist/cli/commands/members.d.ts +11 -0
- package/dist/cli/commands/members.js +230 -0
- package/dist/cli/commands/members.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.js +90 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/start.d.ts +7 -0
- package/dist/cli/commands/start.js +381 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +10 -0
- package/dist/cli/commands/sync.js +121 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/graph.d.ts +169 -0
- package/dist/core/graph.js +558 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/types.d.ts +216 -0
- package/dist/core/types.js +71 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/user-config.d.ts +31 -0
- package/dist/core/user-config.js +50 -0
- package/dist/core/user-config.js.map +1 -0
- package/dist/core/validation.d.ts +2371 -0
- package/dist/core/validation.js +432 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/workspace-manager.d.ts +104 -0
- package/dist/core/workspace-manager.js +432 -0
- package/dist/core/workspace-manager.js.map +1 -0
- package/dist/core/workspace.d.ts +103 -0
- package/dist/core/workspace.js +306 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +84 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/plugin-loader.d.ts +31 -0
- package/dist/server/plugin-loader.js +81 -0
- package/dist/server/plugin-loader.js.map +1 -0
- package/dist/server/routes/chat.d.ts +11 -0
- package/dist/server/routes/chat.js +66 -0
- package/dist/server/routes/chat.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +9 -0
- package/dist/server/routes/graph-api.js +72 -0
- package/dist/server/routes/graph-api.js.map +1 -0
- package/dist/server/routes/webhook.d.ts +14 -0
- package/dist/server/routes/webhook.js +69 -0
- package/dist/server/routes/webhook.js.map +1 -0
- package/dist/services/assets/base.d.ts +69 -0
- package/dist/services/assets/base.js +113 -0
- package/dist/services/assets/base.js.map +1 -0
- package/dist/services/assets/index.d.ts +36 -0
- package/dist/services/assets/index.js +89 -0
- package/dist/services/assets/index.js.map +1 -0
- package/dist/services/assets/local.d.ts +42 -0
- package/dist/services/assets/local.js +161 -0
- package/dist/services/assets/local.js.map +1 -0
- package/dist/services/assets/r2.d.ts +36 -0
- package/dist/services/assets/r2.js +182 -0
- package/dist/services/assets/r2.js.map +1 -0
- package/dist/services/csv-service.d.ts +36 -0
- package/dist/services/csv-service.js +143 -0
- package/dist/services/csv-service.js.map +1 -0
- package/dist/services/git.d.ts +99 -0
- package/dist/services/git.js +306 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/github-provisioner.d.ts +30 -0
- package/dist/services/github-provisioner.js +89 -0
- package/dist/services/github-provisioner.js.map +1 -0
- package/dist/services/markdown.d.ts +82 -0
- package/dist/services/markdown.js +338 -0
- package/dist/services/markdown.js.map +1 -0
- package/dist/services/memory-service.d.ts +74 -0
- package/dist/services/memory-service.js +183 -0
- package/dist/services/memory-service.js.map +1 -0
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.js +55 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/preflight.d.ts +44 -0
- package/dist/utils/preflight.js +95 -0
- package/dist/utils/preflight.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local filesystem asset backend
|
|
3
|
+
*
|
|
4
|
+
* Stores assets in .studiograph/assets/ directory
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, unlinkSync, statSync } from 'fs';
|
|
7
|
+
import { join, dirname, relative } from 'path';
|
|
8
|
+
import { getMediaType } from './base.js';
|
|
9
|
+
export class LocalAssetBackend {
|
|
10
|
+
basePath;
|
|
11
|
+
constructor(basePath) {
|
|
12
|
+
this.basePath = basePath;
|
|
13
|
+
this.ensureBaseDirectory();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Ensure base directory exists
|
|
17
|
+
*/
|
|
18
|
+
ensureBaseDirectory() {
|
|
19
|
+
if (!existsSync(this.basePath)) {
|
|
20
|
+
mkdirSync(this.basePath, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get full file path for a key
|
|
25
|
+
*/
|
|
26
|
+
getFilePath(key) {
|
|
27
|
+
return join(this.basePath, key);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Upload asset to local filesystem
|
|
31
|
+
*/
|
|
32
|
+
async upload(key, buffer, contentType) {
|
|
33
|
+
const filePath = this.getFilePath(key);
|
|
34
|
+
const dir = dirname(filePath);
|
|
35
|
+
// Ensure directory exists
|
|
36
|
+
if (!existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
// Write file
|
|
40
|
+
writeFileSync(filePath, buffer);
|
|
41
|
+
// Get file size
|
|
42
|
+
const stats = statSync(filePath);
|
|
43
|
+
const filename = key.split('/').pop() || key;
|
|
44
|
+
// Generate relative path for markdown
|
|
45
|
+
// For local files, we use relative paths from workspace root
|
|
46
|
+
const relativePath = relative(process.cwd(), filePath);
|
|
47
|
+
return {
|
|
48
|
+
filename,
|
|
49
|
+
size: stats.size,
|
|
50
|
+
contentType,
|
|
51
|
+
url: `file:///${filePath}`, // Local file URL
|
|
52
|
+
path: relativePath, // Relative path for markdown
|
|
53
|
+
mediaType: getMediaType(contentType),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* List assets with a given prefix
|
|
58
|
+
*/
|
|
59
|
+
async list(prefix) {
|
|
60
|
+
const dirPath = this.getFilePath(prefix);
|
|
61
|
+
if (!existsSync(dirPath)) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const assets = [];
|
|
65
|
+
try {
|
|
66
|
+
const files = readdirSync(dirPath);
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const filePath = join(dirPath, file);
|
|
69
|
+
const stats = statSync(filePath);
|
|
70
|
+
// Skip directories
|
|
71
|
+
if (stats.isDirectory()) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const key = join(prefix, file);
|
|
75
|
+
const filename = file;
|
|
76
|
+
const relativePath = relative(process.cwd(), filePath);
|
|
77
|
+
// Try to detect content type from filename
|
|
78
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
79
|
+
const contentType = this.guessContentType(ext);
|
|
80
|
+
assets.push({
|
|
81
|
+
filename,
|
|
82
|
+
size: stats.size,
|
|
83
|
+
contentType,
|
|
84
|
+
url: `file:///${filePath}`,
|
|
85
|
+
path: relativePath,
|
|
86
|
+
mediaType: getMediaType(contentType),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Directory doesn't exist or can't be read
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
return assets;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Delete a single asset
|
|
98
|
+
*/
|
|
99
|
+
async delete(key) {
|
|
100
|
+
const filePath = this.getFilePath(key);
|
|
101
|
+
if (!existsSync(filePath)) {
|
|
102
|
+
throw new Error(`Asset not found: ${key}`);
|
|
103
|
+
}
|
|
104
|
+
unlinkSync(filePath);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete all assets with a given prefix
|
|
108
|
+
*/
|
|
109
|
+
async deleteAll(prefix) {
|
|
110
|
+
const dirPath = this.getFilePath(prefix);
|
|
111
|
+
if (!existsSync(dirPath)) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
let count = 0;
|
|
115
|
+
try {
|
|
116
|
+
const files = readdirSync(dirPath);
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
const filePath = join(dirPath, file);
|
|
119
|
+
const stats = statSync(filePath);
|
|
120
|
+
// Only delete files, not directories
|
|
121
|
+
if (!stats.isDirectory()) {
|
|
122
|
+
unlinkSync(filePath);
|
|
123
|
+
count++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw new Error(`Failed to delete assets: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
129
|
+
}
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if an asset exists
|
|
134
|
+
*/
|
|
135
|
+
async exists(key) {
|
|
136
|
+
const filePath = this.getFilePath(key);
|
|
137
|
+
return existsSync(filePath);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Guess content type from file extension
|
|
141
|
+
*/
|
|
142
|
+
guessContentType(ext) {
|
|
143
|
+
const contentTypes = {
|
|
144
|
+
png: 'image/png',
|
|
145
|
+
jpg: 'image/jpeg',
|
|
146
|
+
jpeg: 'image/jpeg',
|
|
147
|
+
gif: 'image/gif',
|
|
148
|
+
webp: 'image/webp',
|
|
149
|
+
svg: 'image/svg+xml',
|
|
150
|
+
mp4: 'video/mp4',
|
|
151
|
+
mov: 'video/quicktime',
|
|
152
|
+
webm: 'video/webm',
|
|
153
|
+
mp3: 'audio/mpeg',
|
|
154
|
+
wav: 'audio/wav',
|
|
155
|
+
pdf: 'application/pdf',
|
|
156
|
+
zip: 'application/zip',
|
|
157
|
+
};
|
|
158
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/services/assets/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 asset backend
|
|
3
|
+
*
|
|
4
|
+
* S3-compatible object storage with zero egress fees
|
|
5
|
+
*/
|
|
6
|
+
import type { AssetBackend, AssetInfo, R2Config } from './base.js';
|
|
7
|
+
export declare class R2AssetBackend implements AssetBackend {
|
|
8
|
+
private client;
|
|
9
|
+
private bucket;
|
|
10
|
+
private publicUrl;
|
|
11
|
+
constructor(config: R2Config);
|
|
12
|
+
/**
|
|
13
|
+
* Upload asset to R2
|
|
14
|
+
*/
|
|
15
|
+
upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
|
|
16
|
+
/**
|
|
17
|
+
* List assets with a given prefix
|
|
18
|
+
*/
|
|
19
|
+
list(prefix: string): Promise<AssetInfo[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Delete a single asset
|
|
22
|
+
*/
|
|
23
|
+
delete(key: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete all assets with a given prefix
|
|
26
|
+
*/
|
|
27
|
+
deleteAll(prefix: string): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if an asset exists
|
|
30
|
+
*/
|
|
31
|
+
exists(key: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Guess content type from file extension
|
|
34
|
+
*/
|
|
35
|
+
private guessContentType;
|
|
36
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 asset backend
|
|
3
|
+
*
|
|
4
|
+
* S3-compatible object storage with zero egress fees
|
|
5
|
+
*/
|
|
6
|
+
import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, DeleteObjectsCommand, HeadObjectCommand, } from '@aws-sdk/client-s3';
|
|
7
|
+
import { getMediaType } from './base.js';
|
|
8
|
+
export class R2AssetBackend {
|
|
9
|
+
client;
|
|
10
|
+
bucket;
|
|
11
|
+
publicUrl;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.bucket = config.bucket;
|
|
14
|
+
this.publicUrl = config.publicUrl.replace(/\/+$/, ''); // Remove trailing slashes
|
|
15
|
+
// Create S3 client configured for R2
|
|
16
|
+
this.client = new S3Client({
|
|
17
|
+
region: 'auto',
|
|
18
|
+
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
19
|
+
credentials: {
|
|
20
|
+
accessKeyId: config.accessKeyId,
|
|
21
|
+
secretAccessKey: config.secretAccessKey,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Upload asset to R2
|
|
27
|
+
*/
|
|
28
|
+
async upload(key, buffer, contentType) {
|
|
29
|
+
try {
|
|
30
|
+
await this.client.send(new PutObjectCommand({
|
|
31
|
+
Bucket: this.bucket,
|
|
32
|
+
Key: key,
|
|
33
|
+
Body: buffer,
|
|
34
|
+
ContentType: contentType,
|
|
35
|
+
CacheControl: 'public, max-age=31536000, immutable', // Cache for 1 year
|
|
36
|
+
}));
|
|
37
|
+
const filename = key.split('/').pop() || key;
|
|
38
|
+
const url = `${this.publicUrl}/${key}`;
|
|
39
|
+
return {
|
|
40
|
+
filename,
|
|
41
|
+
size: buffer.length,
|
|
42
|
+
contentType,
|
|
43
|
+
url,
|
|
44
|
+
path: url, // For R2, use full URL in markdown
|
|
45
|
+
mediaType: getMediaType(contentType),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Failed to upload to R2: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* List assets with a given prefix
|
|
54
|
+
*/
|
|
55
|
+
async list(prefix) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
58
|
+
Bucket: this.bucket,
|
|
59
|
+
Prefix: prefix,
|
|
60
|
+
}));
|
|
61
|
+
const assets = [];
|
|
62
|
+
for (const obj of response.Contents || []) {
|
|
63
|
+
if (!obj.Key)
|
|
64
|
+
continue;
|
|
65
|
+
// Skip if it's a "directory" (ends with /)
|
|
66
|
+
if (obj.Key.endsWith('/'))
|
|
67
|
+
continue;
|
|
68
|
+
const filename = obj.Key.split('/').pop() || obj.Key;
|
|
69
|
+
// Skip if filename contains additional slashes (nested directories)
|
|
70
|
+
const relativePath = obj.Key.replace(prefix, '');
|
|
71
|
+
if (relativePath.includes('/'))
|
|
72
|
+
continue;
|
|
73
|
+
const url = `${this.publicUrl}/${obj.Key}`;
|
|
74
|
+
// Try to get content type from metadata
|
|
75
|
+
let contentType = 'application/octet-stream';
|
|
76
|
+
try {
|
|
77
|
+
const headResponse = await this.client.send(new HeadObjectCommand({
|
|
78
|
+
Bucket: this.bucket,
|
|
79
|
+
Key: obj.Key,
|
|
80
|
+
}));
|
|
81
|
+
contentType = headResponse.ContentType || contentType;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// If HEAD fails, guess from extension
|
|
85
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
86
|
+
contentType = this.guessContentType(ext);
|
|
87
|
+
}
|
|
88
|
+
assets.push({
|
|
89
|
+
filename,
|
|
90
|
+
size: obj.Size || 0,
|
|
91
|
+
contentType,
|
|
92
|
+
url,
|
|
93
|
+
path: url,
|
|
94
|
+
mediaType: getMediaType(contentType),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return assets;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new Error(`Failed to list assets from R2: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Delete a single asset
|
|
105
|
+
*/
|
|
106
|
+
async delete(key) {
|
|
107
|
+
try {
|
|
108
|
+
await this.client.send(new DeleteObjectCommand({
|
|
109
|
+
Bucket: this.bucket,
|
|
110
|
+
Key: key,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new Error(`Failed to delete from R2: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Delete all assets with a given prefix
|
|
119
|
+
*/
|
|
120
|
+
async deleteAll(prefix) {
|
|
121
|
+
try {
|
|
122
|
+
// First, list all objects with the prefix
|
|
123
|
+
const response = await this.client.send(new ListObjectsV2Command({
|
|
124
|
+
Bucket: this.bucket,
|
|
125
|
+
Prefix: prefix,
|
|
126
|
+
}));
|
|
127
|
+
const objects = response.Contents || [];
|
|
128
|
+
if (objects.length === 0) {
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
// Delete all objects
|
|
132
|
+
await this.client.send(new DeleteObjectsCommand({
|
|
133
|
+
Bucket: this.bucket,
|
|
134
|
+
Delete: {
|
|
135
|
+
Objects: objects.map(obj => ({ Key: obj.Key })),
|
|
136
|
+
Quiet: true,
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
139
|
+
return objects.length;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new Error(`Failed to delete assets from R2: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if an asset exists
|
|
147
|
+
*/
|
|
148
|
+
async exists(key) {
|
|
149
|
+
try {
|
|
150
|
+
await this.client.send(new HeadObjectCommand({
|
|
151
|
+
Bucket: this.bucket,
|
|
152
|
+
Key: key,
|
|
153
|
+
}));
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Guess content type from file extension
|
|
162
|
+
*/
|
|
163
|
+
guessContentType(ext) {
|
|
164
|
+
const contentTypes = {
|
|
165
|
+
png: 'image/png',
|
|
166
|
+
jpg: 'image/jpeg',
|
|
167
|
+
jpeg: 'image/jpeg',
|
|
168
|
+
gif: 'image/gif',
|
|
169
|
+
webp: 'image/webp',
|
|
170
|
+
svg: 'image/svg+xml',
|
|
171
|
+
mp4: 'video/mp4',
|
|
172
|
+
mov: 'video/quicktime',
|
|
173
|
+
webm: 'video/webm',
|
|
174
|
+
mp3: 'audio/mpeg',
|
|
175
|
+
wav: 'audio/wav',
|
|
176
|
+
pdf: 'application/pdf',
|
|
177
|
+
zip: 'application/zip',
|
|
178
|
+
};
|
|
179
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=r2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.js","sourceRoot":"","sources":["../../../src/services/assets/r2.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV Service
|
|
3
|
+
*
|
|
4
|
+
* Parses, stringifies, validates, and filters CSV data for dataset entities.
|
|
5
|
+
* Uses csv-parse and csv-stringify for reliable CSV handling.
|
|
6
|
+
*/
|
|
7
|
+
import type { ColumnDefinition, DatasetRow } from '../core/validation.js';
|
|
8
|
+
export interface DatasetFilter {
|
|
9
|
+
[column: string]: string | number | boolean | {
|
|
10
|
+
gte?: any;
|
|
11
|
+
lte?: any;
|
|
12
|
+
contains?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export declare class CSVService {
|
|
16
|
+
/**
|
|
17
|
+
* Parse CSV text into typed rows using the declared schema.
|
|
18
|
+
* Returns an empty array for CSV with headers only (no data rows).
|
|
19
|
+
*/
|
|
20
|
+
parseRows(csvText: string, schema: ColumnDefinition[]): DatasetRow[];
|
|
21
|
+
/**
|
|
22
|
+
* Stringify rows back to CSV text (header row + data rows).
|
|
23
|
+
*/
|
|
24
|
+
stringifyRows(rows: DatasetRow[], schema: ColumnDefinition[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* Validate and coerce a raw string record against the schema.
|
|
27
|
+
* Throws on missing required fields or type coercion failure.
|
|
28
|
+
*/
|
|
29
|
+
validateRow(row: Record<string, string>, schema: ColumnDefinition[]): DatasetRow;
|
|
30
|
+
/**
|
|
31
|
+
* Filter rows using a DatasetFilter.
|
|
32
|
+
* Supports equality, { gte, lte } for numbers/dates, { contains } for strings.
|
|
33
|
+
*/
|
|
34
|
+
filterRows(rows: DatasetRow[], filter: DatasetFilter): DatasetRow[];
|
|
35
|
+
private coerceValue;
|
|
36
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV Service
|
|
3
|
+
*
|
|
4
|
+
* Parses, stringifies, validates, and filters CSV data for dataset entities.
|
|
5
|
+
* Uses csv-parse and csv-stringify for reliable CSV handling.
|
|
6
|
+
*/
|
|
7
|
+
import { parse } from 'csv-parse/sync';
|
|
8
|
+
import { stringify } from 'csv-stringify/sync';
|
|
9
|
+
export class CSVService {
|
|
10
|
+
/**
|
|
11
|
+
* Parse CSV text into typed rows using the declared schema.
|
|
12
|
+
* Returns an empty array for CSV with headers only (no data rows).
|
|
13
|
+
*/
|
|
14
|
+
parseRows(csvText, schema) {
|
|
15
|
+
const trimmed = csvText.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const records = parse(trimmed, {
|
|
20
|
+
columns: true,
|
|
21
|
+
skip_empty_lines: true,
|
|
22
|
+
trim: true,
|
|
23
|
+
});
|
|
24
|
+
return records.map(record => this.validateRow(record, schema));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stringify rows back to CSV text (header row + data rows).
|
|
28
|
+
*/
|
|
29
|
+
stringifyRows(rows, schema) {
|
|
30
|
+
const columns = schema.map(col => col.name);
|
|
31
|
+
if (rows.length === 0) {
|
|
32
|
+
return columns.join(',') + '\n';
|
|
33
|
+
}
|
|
34
|
+
return stringify(rows, {
|
|
35
|
+
header: true,
|
|
36
|
+
columns,
|
|
37
|
+
cast: {
|
|
38
|
+
boolean: (value) => String(value),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate and coerce a raw string record against the schema.
|
|
44
|
+
* Throws on missing required fields or type coercion failure.
|
|
45
|
+
*/
|
|
46
|
+
validateRow(row, schema) {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const col of schema) {
|
|
49
|
+
const raw = row[col.name];
|
|
50
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
51
|
+
if (col.required) {
|
|
52
|
+
throw new Error(`Missing required field: ${col.name}`);
|
|
53
|
+
}
|
|
54
|
+
result[col.name] = null;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
result[col.name] = this.coerceValue(raw, col);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Filter rows using a DatasetFilter.
|
|
63
|
+
* Supports equality, { gte, lte } for numbers/dates, { contains } for strings.
|
|
64
|
+
*/
|
|
65
|
+
filterRows(rows, filter) {
|
|
66
|
+
return rows.filter(row => {
|
|
67
|
+
for (const [column, condition] of Object.entries(filter)) {
|
|
68
|
+
const value = row[column];
|
|
69
|
+
if (typeof condition === 'object' && condition !== null) {
|
|
70
|
+
const { gte, lte, contains } = condition;
|
|
71
|
+
if (contains !== undefined) {
|
|
72
|
+
if (typeof value !== 'string' || !value.includes(contains)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (gte !== undefined) {
|
|
77
|
+
if (value === null || value === undefined)
|
|
78
|
+
return false;
|
|
79
|
+
if (typeof value === 'string' && typeof gte === 'string') {
|
|
80
|
+
if (value < gte)
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (value < gte)
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (lte !== undefined) {
|
|
89
|
+
if (value === null || value === undefined)
|
|
90
|
+
return false;
|
|
91
|
+
if (typeof value === 'string' && typeof lte === 'string') {
|
|
92
|
+
if (value > lte)
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
if (value > lte)
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Equality check
|
|
103
|
+
if (value !== condition) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
coerceValue(raw, col) {
|
|
112
|
+
switch (col.type) {
|
|
113
|
+
case 'number': {
|
|
114
|
+
const n = Number(raw);
|
|
115
|
+
if (isNaN(n)) {
|
|
116
|
+
throw new Error(`Invalid number value for column "${col.name}": ${raw}`);
|
|
117
|
+
}
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
case 'boolean': {
|
|
121
|
+
const lower = raw.toLowerCase();
|
|
122
|
+
if (lower === 'true' || lower === '1' || lower === 'yes')
|
|
123
|
+
return true;
|
|
124
|
+
if (lower === 'false' || lower === '0' || lower === 'no')
|
|
125
|
+
return false;
|
|
126
|
+
throw new Error(`Invalid boolean value for column "${col.name}": ${raw}`);
|
|
127
|
+
}
|
|
128
|
+
case 'date': {
|
|
129
|
+
// Store dates as ISO string; validate that the value is parseable
|
|
130
|
+
const d = new Date(raw);
|
|
131
|
+
if (isNaN(d.getTime())) {
|
|
132
|
+
throw new Error(`Invalid date value for column "${col.name}": ${raw}`);
|
|
133
|
+
}
|
|
134
|
+
// Return the original date string (YYYY-MM-DD) rather than converting to full ISO
|
|
135
|
+
return raw;
|
|
136
|
+
}
|
|
137
|
+
case 'string':
|
|
138
|
+
default:
|
|
139
|
+
return raw;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=csv-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-service.js","sourceRoot":"","sources":["../../src/services/csv-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git service for Studiograph
|
|
3
|
+
*
|
|
4
|
+
* Handles Git operations with transaction-based commits,
|
|
5
|
+
* proper user attribution, and security.
|
|
6
|
+
*/
|
|
7
|
+
export interface GitUser {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
email: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GitChange {
|
|
13
|
+
operation: 'create' | 'update' | 'delete';
|
|
14
|
+
filePath: string;
|
|
15
|
+
entityType: string;
|
|
16
|
+
entityId: string;
|
|
17
|
+
}
|
|
18
|
+
export interface GitCommit {
|
|
19
|
+
hash: string;
|
|
20
|
+
author: {
|
|
21
|
+
name: string;
|
|
22
|
+
email: string;
|
|
23
|
+
};
|
|
24
|
+
timestamp: number;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class GitService {
|
|
28
|
+
private repoPath;
|
|
29
|
+
constructor(repoPath: string);
|
|
30
|
+
/**
|
|
31
|
+
* Ensure the repo path exists and is a directory
|
|
32
|
+
*/
|
|
33
|
+
private ensureValidPath;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize Git repository if not already initialized
|
|
36
|
+
*/
|
|
37
|
+
private initializeIfNeeded;
|
|
38
|
+
/**
|
|
39
|
+
* Check if repository has any commits
|
|
40
|
+
*/
|
|
41
|
+
hasCommits(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get current branch name
|
|
44
|
+
*/
|
|
45
|
+
getCurrentBranch(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Create a transaction for batched commits
|
|
48
|
+
*/
|
|
49
|
+
createTransaction(): GitTransaction;
|
|
50
|
+
/**
|
|
51
|
+
* Get commit history
|
|
52
|
+
*/
|
|
53
|
+
getHistory(limit?: number): GitCommit[];
|
|
54
|
+
/**
|
|
55
|
+
* Get file history for a specific file
|
|
56
|
+
*/
|
|
57
|
+
getFileHistory(filePath: string, limit?: number): GitCommit[];
|
|
58
|
+
/**
|
|
59
|
+
* Get current status (modified, added, deleted files)
|
|
60
|
+
*/
|
|
61
|
+
getStatus(): {
|
|
62
|
+
modified: string[];
|
|
63
|
+
added: string[];
|
|
64
|
+
deleted: string[];
|
|
65
|
+
untracked: string[];
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Check if there are uncommitted changes
|
|
69
|
+
*/
|
|
70
|
+
hasUncommittedChanges(): boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Transaction class for batched Git commits
|
|
74
|
+
*/
|
|
75
|
+
export declare class GitTransaction {
|
|
76
|
+
private repoPath;
|
|
77
|
+
private changes;
|
|
78
|
+
constructor(repoPath: string);
|
|
79
|
+
/**
|
|
80
|
+
* Add a change to this transaction
|
|
81
|
+
*/
|
|
82
|
+
add(change: GitChange): void;
|
|
83
|
+
/**
|
|
84
|
+
* Get all changes in this transaction
|
|
85
|
+
*/
|
|
86
|
+
getChanges(): GitChange[];
|
|
87
|
+
/**
|
|
88
|
+
* Commit all changes in this transaction
|
|
89
|
+
*/
|
|
90
|
+
commit(user: GitUser, message?: string): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Generate commit message from changes
|
|
93
|
+
*/
|
|
94
|
+
private generateMessage;
|
|
95
|
+
/**
|
|
96
|
+
* Rollback all changes (git reset)
|
|
97
|
+
*/
|
|
98
|
+
rollback(): Promise<void>;
|
|
99
|
+
}
|