@objectql/server 1.8.1 → 1.8.3
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/CHANGELOG.md +21 -0
- package/dist/adapters/node.d.ts +9 -1
- package/dist/adapters/node.js +30 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/file-handler.d.ts +50 -0
- package/dist/file-handler.js +354 -0
- package/dist/file-handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/storage.d.ts +35 -0
- package/dist/storage.js +181 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +72 -0
- package/package.json +16 -4
- package/src/adapters/node.ts +44 -2
- package/src/file-handler.ts +414 -0
- package/src/index.ts +2 -0
- package/src/storage.ts +171 -0
- package/src/types.ts +79 -0
- package/test/file-upload-integration.example.ts +201 -0
- package/test/file-validation.test.ts +131 -0
- package/test/storage.test.ts +104 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/storage.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MemoryFileStorage = exports.LocalFileStorage = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
/**
|
|
41
|
+
* Local filesystem storage implementation for file attachments
|
|
42
|
+
*/
|
|
43
|
+
class LocalFileStorage {
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.baseDir = options.baseDir;
|
|
46
|
+
this.baseUrl = options.baseUrl;
|
|
47
|
+
// Ensure base directory exists
|
|
48
|
+
if (!fs.existsSync(this.baseDir)) {
|
|
49
|
+
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async save(file, filename, mimeType, options) {
|
|
53
|
+
// Generate unique ID for the file
|
|
54
|
+
const id = crypto.randomBytes(16).toString('hex');
|
|
55
|
+
const ext = path.extname(filename);
|
|
56
|
+
const basename = path.basename(filename, ext);
|
|
57
|
+
const storedFilename = `${id}${ext}`;
|
|
58
|
+
// Determine storage path
|
|
59
|
+
let folder = (options === null || options === void 0 ? void 0 : options.folder) || 'uploads';
|
|
60
|
+
if (options === null || options === void 0 ? void 0 : options.object) {
|
|
61
|
+
folder = path.join(folder, options.object);
|
|
62
|
+
}
|
|
63
|
+
const folderPath = path.join(this.baseDir, folder);
|
|
64
|
+
if (!fs.existsSync(folderPath)) {
|
|
65
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
const filePath = path.join(folderPath, storedFilename);
|
|
68
|
+
// Write file to disk (async for better performance)
|
|
69
|
+
await fs.promises.writeFile(filePath, file);
|
|
70
|
+
// Generate public URL
|
|
71
|
+
const url = this.getPublicUrl(path.join(folder, storedFilename));
|
|
72
|
+
const attachmentData = {
|
|
73
|
+
id,
|
|
74
|
+
name: storedFilename,
|
|
75
|
+
url,
|
|
76
|
+
size: file.length,
|
|
77
|
+
type: mimeType,
|
|
78
|
+
original_name: filename,
|
|
79
|
+
uploaded_at: new Date().toISOString(),
|
|
80
|
+
uploaded_by: options === null || options === void 0 ? void 0 : options.userId
|
|
81
|
+
};
|
|
82
|
+
return attachmentData;
|
|
83
|
+
}
|
|
84
|
+
async get(fileId) {
|
|
85
|
+
try {
|
|
86
|
+
// Search for file in the upload directory
|
|
87
|
+
const found = this.findFile(this.baseDir, fileId);
|
|
88
|
+
if (!found) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// Use async read for better performance
|
|
92
|
+
return await fs.promises.readFile(found);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Error reading file:', error);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async delete(fileId) {
|
|
100
|
+
try {
|
|
101
|
+
const found = this.findFile(this.baseDir, fileId);
|
|
102
|
+
if (!found) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Use async unlink for better performance
|
|
106
|
+
await fs.promises.unlink(found);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error('Error deleting file:', error);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
getPublicUrl(filePath) {
|
|
115
|
+
// Normalize path separators for URLs
|
|
116
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
117
|
+
return `${this.baseUrl}/${normalizedPath}`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Recursively search for a file by ID
|
|
121
|
+
*/
|
|
122
|
+
findFile(dir, fileId) {
|
|
123
|
+
const files = fs.readdirSync(dir);
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const filePath = path.join(dir, file);
|
|
126
|
+
const stat = fs.statSync(filePath);
|
|
127
|
+
if (stat.isDirectory()) {
|
|
128
|
+
const found = this.findFile(filePath, fileId);
|
|
129
|
+
if (found) {
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (file.startsWith(fileId)) {
|
|
134
|
+
return filePath;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.LocalFileStorage = LocalFileStorage;
|
|
141
|
+
/**
|
|
142
|
+
* Memory storage implementation for testing
|
|
143
|
+
*/
|
|
144
|
+
class MemoryFileStorage {
|
|
145
|
+
constructor(options) {
|
|
146
|
+
this.files = new Map();
|
|
147
|
+
this.baseUrl = options.baseUrl;
|
|
148
|
+
}
|
|
149
|
+
async save(file, filename, mimeType, options) {
|
|
150
|
+
const id = crypto.randomBytes(16).toString('hex');
|
|
151
|
+
const ext = path.extname(filename);
|
|
152
|
+
const storedFilename = `${id}${ext}`;
|
|
153
|
+
const attachmentData = {
|
|
154
|
+
id,
|
|
155
|
+
name: storedFilename,
|
|
156
|
+
url: this.getPublicUrl(storedFilename),
|
|
157
|
+
size: file.length,
|
|
158
|
+
type: mimeType,
|
|
159
|
+
original_name: filename,
|
|
160
|
+
uploaded_at: new Date().toISOString(),
|
|
161
|
+
uploaded_by: options === null || options === void 0 ? void 0 : options.userId
|
|
162
|
+
};
|
|
163
|
+
this.files.set(id, { buffer: file, metadata: attachmentData });
|
|
164
|
+
return attachmentData;
|
|
165
|
+
}
|
|
166
|
+
async get(fileId) {
|
|
167
|
+
const entry = this.files.get(fileId);
|
|
168
|
+
return entry ? entry.buffer : null;
|
|
169
|
+
}
|
|
170
|
+
async delete(fileId) {
|
|
171
|
+
return this.files.delete(fileId);
|
|
172
|
+
}
|
|
173
|
+
getPublicUrl(filePath) {
|
|
174
|
+
return `${this.baseUrl}/${filePath}`;
|
|
175
|
+
}
|
|
176
|
+
clear() {
|
|
177
|
+
this.files.clear();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.MemoryFileStorage = MemoryFileStorage;
|
|
181
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -70,3 +70,75 @@ export interface ObjectQLResponse {
|
|
|
70
70
|
};
|
|
71
71
|
[key: string]: any;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Attachment/File metadata structure
|
|
75
|
+
*/
|
|
76
|
+
export interface AttachmentData {
|
|
77
|
+
/** Unique identifier for this file */
|
|
78
|
+
id: string;
|
|
79
|
+
/** File name (e.g., "invoice.pdf") */
|
|
80
|
+
name: string;
|
|
81
|
+
/** Publicly accessible URL to the file */
|
|
82
|
+
url: string;
|
|
83
|
+
/** File size in bytes */
|
|
84
|
+
size: number;
|
|
85
|
+
/** MIME type (e.g., "application/pdf", "image/jpeg") */
|
|
86
|
+
type: string;
|
|
87
|
+
/** Original filename as uploaded by user */
|
|
88
|
+
original_name?: string;
|
|
89
|
+
/** Upload timestamp (ISO 8601) */
|
|
90
|
+
uploaded_at?: string;
|
|
91
|
+
/** User ID who uploaded the file */
|
|
92
|
+
uploaded_by?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Image-specific attachment data with metadata
|
|
96
|
+
*/
|
|
97
|
+
export interface ImageAttachmentData extends AttachmentData {
|
|
98
|
+
/** Image width in pixels */
|
|
99
|
+
width?: number;
|
|
100
|
+
/** Image height in pixels */
|
|
101
|
+
height?: number;
|
|
102
|
+
/** Thumbnail URL (if generated) */
|
|
103
|
+
thumbnail_url?: string;
|
|
104
|
+
/** Alternative sizes/versions */
|
|
105
|
+
variants?: {
|
|
106
|
+
small?: string;
|
|
107
|
+
medium?: string;
|
|
108
|
+
large?: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* File storage provider interface
|
|
113
|
+
*/
|
|
114
|
+
export interface IFileStorage {
|
|
115
|
+
/**
|
|
116
|
+
* Save a file and return its metadata
|
|
117
|
+
*/
|
|
118
|
+
save(file: Buffer, filename: string, mimeType: string, options?: FileStorageOptions): Promise<AttachmentData>;
|
|
119
|
+
/**
|
|
120
|
+
* Retrieve a file by its ID or path
|
|
121
|
+
*/
|
|
122
|
+
get(fileId: string): Promise<Buffer | null>;
|
|
123
|
+
/**
|
|
124
|
+
* Delete a file
|
|
125
|
+
*/
|
|
126
|
+
delete(fileId: string): Promise<boolean>;
|
|
127
|
+
/**
|
|
128
|
+
* Generate a public URL for a file
|
|
129
|
+
*/
|
|
130
|
+
getPublicUrl(fileId: string): string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Options for file storage operations
|
|
134
|
+
*/
|
|
135
|
+
export interface FileStorageOptions {
|
|
136
|
+
/** Logical folder/path for organization */
|
|
137
|
+
folder?: string;
|
|
138
|
+
/** Object name (for context/validation) */
|
|
139
|
+
object?: string;
|
|
140
|
+
/** Field name (for validation against field config) */
|
|
141
|
+
field?: string;
|
|
142
|
+
/** User ID who uploaded the file */
|
|
143
|
+
userId?: string;
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectql/server",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
|
+
"description": "HTTP server adapter for ObjectQL - Express/NestJS compatible with GraphQL and REST API support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"objectql",
|
|
7
|
+
"server",
|
|
8
|
+
"http",
|
|
9
|
+
"api",
|
|
10
|
+
"rest",
|
|
11
|
+
"graphql",
|
|
12
|
+
"express",
|
|
13
|
+
"nestjs",
|
|
14
|
+
"adapter",
|
|
15
|
+
"backend"
|
|
16
|
+
],
|
|
4
17
|
"license": "MIT",
|
|
5
|
-
"description": "HTTP Server Adapter for ObjectQL",
|
|
6
18
|
"main": "dist/index.js",
|
|
7
19
|
"types": "dist/index.d.ts",
|
|
8
20
|
"dependencies": {
|
|
9
21
|
"graphql": "^16.8.1",
|
|
10
22
|
"@graphql-tools/schema": "^10.0.2",
|
|
11
23
|
"js-yaml": "^4.1.1",
|
|
12
|
-
"@objectql/core": "1.8.
|
|
13
|
-
"@objectql/types": "1.8.
|
|
24
|
+
"@objectql/core": "1.8.3",
|
|
25
|
+
"@objectql/types": "1.8.3"
|
|
14
26
|
},
|
|
15
27
|
"devDependencies": {
|
|
16
28
|
"@types/js-yaml": "^4.0.9",
|
package/src/adapters/node.ts
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
import { IObjectQL } from '@objectql/types';
|
|
2
2
|
import { ObjectQLServer } from '../server';
|
|
3
|
-
import { ObjectQLRequest, ErrorCode } from '../types';
|
|
3
|
+
import { ObjectQLRequest, ErrorCode, IFileStorage } from '../types';
|
|
4
4
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
5
5
|
import { generateOpenAPI } from '../openapi';
|
|
6
|
+
import { createFileUploadHandler, createBatchFileUploadHandler, createFileDownloadHandler } from '../file-handler';
|
|
7
|
+
import { LocalFileStorage } from '../storage';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for createNodeHandler
|
|
11
|
+
*/
|
|
12
|
+
export interface NodeHandlerOptions {
|
|
13
|
+
/** File storage provider (defaults to LocalFileStorage) */
|
|
14
|
+
fileStorage?: IFileStorage;
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
/**
|
|
8
18
|
* Creates a standard Node.js HTTP request handler.
|
|
9
19
|
*/
|
|
10
|
-
export function createNodeHandler(app: IObjectQL) {
|
|
20
|
+
export function createNodeHandler(app: IObjectQL, options?: NodeHandlerOptions) {
|
|
11
21
|
const server = new ObjectQLServer(app);
|
|
22
|
+
|
|
23
|
+
// Initialize file storage
|
|
24
|
+
const fileStorage = options?.fileStorage || new LocalFileStorage({
|
|
25
|
+
baseDir: process.env.OBJECTQL_UPLOAD_DIR || './uploads',
|
|
26
|
+
baseUrl: process.env.OBJECTQL_BASE_URL || 'http://localhost:3000/api/files'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Create file handlers
|
|
30
|
+
const uploadHandler = createFileUploadHandler(fileStorage, app);
|
|
31
|
+
const batchUploadHandler = createBatchFileUploadHandler(fileStorage, app);
|
|
32
|
+
const downloadHandler = createFileDownloadHandler(fileStorage);
|
|
12
33
|
|
|
13
34
|
|
|
14
35
|
return async (req: IncomingMessage & { body?: any }, res: ServerResponse) => {
|
|
@@ -165,6 +186,27 @@ export function createNodeHandler(app: IObjectQL) {
|
|
|
165
186
|
}
|
|
166
187
|
}
|
|
167
188
|
|
|
189
|
+
// File Upload Endpoints
|
|
190
|
+
// POST /api/files/upload - Single file upload
|
|
191
|
+
if (pathName === '/api/files/upload' && method === 'POST') {
|
|
192
|
+
await uploadHandler(req, res);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// POST /api/files/upload/batch - Batch file upload
|
|
197
|
+
if (pathName === '/api/files/upload/batch' && method === 'POST') {
|
|
198
|
+
await batchUploadHandler(req, res);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// GET /api/files/:fileId - Download file
|
|
203
|
+
const fileMatch = pathName.match(/^\/api\/files\/([^/]+)$/);
|
|
204
|
+
if (fileMatch && method === 'GET') {
|
|
205
|
+
const fileId = fileMatch[1];
|
|
206
|
+
await downloadHandler(req, res, fileId);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
168
210
|
// Fallback or 404
|
|
169
211
|
if (req.method === 'POST') {
|
|
170
212
|
// Fallback for root POSTs if people forget /api/objectql but send to /api something
|