create-ifc-lite 1.6.0 → 1.7.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/dist/index.js +10 -1250
- package/dist/templates/basic.d.ts +4 -0
- package/dist/templates/basic.js +96 -0
- package/dist/templates/server-native.d.ts +5 -0
- package/dist/templates/server-native.js +406 -0
- package/dist/templates/server.d.ts +4 -0
- package/dist/templates/server.js +613 -0
- package/dist/utils/config-fixers.d.ts +24 -0
- package/dist/utils/config-fixers.js +143 -0
- package/dist/utils/download.d.ts +7 -0
- package/dist/utils/download.js +63 -0
- package/package.json +1 -1
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getLatestVersion } from '../utils/config-fixers.js';
|
|
7
|
+
/**
|
|
8
|
+
* Scaffold a Docker-based IFC processing server with TypeScript client examples.
|
|
9
|
+
*/
|
|
10
|
+
export function createServerTemplate(targetDir, projectName) {
|
|
11
|
+
const latestVersion = getLatestVersion();
|
|
12
|
+
// docker-compose.yml - Production configuration
|
|
13
|
+
writeFileSync(join(targetDir, 'docker-compose.yml'), `# IFC-Lite Server - Production Configuration
|
|
14
|
+
# Start with: docker compose up -d
|
|
15
|
+
|
|
16
|
+
services:
|
|
17
|
+
ifc-server:
|
|
18
|
+
image: ghcr.io/louistrue/ifc-lite-server:latest
|
|
19
|
+
container_name: ${projectName}-server
|
|
20
|
+
ports:
|
|
21
|
+
- "\${PORT:-3001}:8080"
|
|
22
|
+
volumes:
|
|
23
|
+
- ifc-cache:/app/cache
|
|
24
|
+
environment:
|
|
25
|
+
- RUST_LOG=\${RUST_LOG:-info}
|
|
26
|
+
- MAX_FILE_SIZE_MB=\${MAX_FILE_SIZE_MB:-500}
|
|
27
|
+
- REQUEST_TIMEOUT_SECS=\${REQUEST_TIMEOUT_SECS:-300}
|
|
28
|
+
- WORKER_THREADS=\${WORKER_THREADS:-4}
|
|
29
|
+
- INITIAL_BATCH_SIZE=\${INITIAL_BATCH_SIZE:-100}
|
|
30
|
+
- MAX_BATCH_SIZE=\${MAX_BATCH_SIZE:-1000}
|
|
31
|
+
- CACHE_MAX_AGE_DAYS=\${CACHE_MAX_AGE_DAYS:-7}
|
|
32
|
+
healthcheck:
|
|
33
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
|
|
34
|
+
interval: 30s
|
|
35
|
+
timeout: 3s
|
|
36
|
+
retries: 3
|
|
37
|
+
start_period: 5s
|
|
38
|
+
restart: unless-stopped
|
|
39
|
+
|
|
40
|
+
volumes:
|
|
41
|
+
ifc-cache:
|
|
42
|
+
name: ${projectName}-cache
|
|
43
|
+
`);
|
|
44
|
+
// docker-compose.dev.yml - Development configuration with live logs
|
|
45
|
+
writeFileSync(join(targetDir, 'docker-compose.dev.yml'), `# IFC-Lite Server - Development Configuration
|
|
46
|
+
# Start with: docker compose -f docker-compose.dev.yml up
|
|
47
|
+
|
|
48
|
+
services:
|
|
49
|
+
ifc-server:
|
|
50
|
+
image: ghcr.io/louistrue/ifc-lite-server:latest
|
|
51
|
+
container_name: ${projectName}-server-dev
|
|
52
|
+
ports:
|
|
53
|
+
- "\${PORT:-3001}:8080"
|
|
54
|
+
volumes:
|
|
55
|
+
- ./cache:/app/cache # Local cache directory for inspection
|
|
56
|
+
environment:
|
|
57
|
+
- RUST_LOG=debug,tower_http=debug,ifc_lite_server=debug
|
|
58
|
+
- MAX_FILE_SIZE_MB=\${MAX_FILE_SIZE_MB:-500}
|
|
59
|
+
- REQUEST_TIMEOUT_SECS=\${REQUEST_TIMEOUT_SECS:-300}
|
|
60
|
+
- WORKER_THREADS=\${WORKER_THREADS:-4}
|
|
61
|
+
- INITIAL_BATCH_SIZE=\${INITIAL_BATCH_SIZE:-100}
|
|
62
|
+
- MAX_BATCH_SIZE=\${MAX_BATCH_SIZE:-1000}
|
|
63
|
+
- CACHE_MAX_AGE_DAYS=\${CACHE_MAX_AGE_DAYS:-30}
|
|
64
|
+
# No restart in dev - easier to debug
|
|
65
|
+
`);
|
|
66
|
+
// .env.example - Documented environment variables
|
|
67
|
+
writeFileSync(join(targetDir, '.env.example'), `# IFC-Lite Server Configuration
|
|
68
|
+
# Copy this file to .env and modify as needed
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# SERVER SETTINGS
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
# Port to expose the server on (maps to internal port 8080)
|
|
75
|
+
PORT=3001
|
|
76
|
+
|
|
77
|
+
# Log level: error, warn, info, debug, trace
|
|
78
|
+
# Use "debug" for development, "info" for production
|
|
79
|
+
RUST_LOG=info
|
|
80
|
+
|
|
81
|
+
# =============================================================================
|
|
82
|
+
# FILE PROCESSING
|
|
83
|
+
# =============================================================================
|
|
84
|
+
|
|
85
|
+
# Maximum IFC file size in megabytes
|
|
86
|
+
# Larger files need more memory and processing time
|
|
87
|
+
MAX_FILE_SIZE_MB=500
|
|
88
|
+
|
|
89
|
+
# Request timeout in seconds
|
|
90
|
+
# Increase for very large files (500MB+)
|
|
91
|
+
REQUEST_TIMEOUT_SECS=300
|
|
92
|
+
|
|
93
|
+
# Number of worker threads for parallel geometry processing
|
|
94
|
+
# Default: number of CPU cores
|
|
95
|
+
# Reduce if running alongside other services
|
|
96
|
+
WORKER_THREADS=4
|
|
97
|
+
|
|
98
|
+
# =============================================================================
|
|
99
|
+
# STREAMING (Progressive Rendering)
|
|
100
|
+
# =============================================================================
|
|
101
|
+
|
|
102
|
+
# Initial batch size for fast first frame (first 3 batches)
|
|
103
|
+
# Smaller = faster first render, but more HTTP overhead
|
|
104
|
+
INITIAL_BATCH_SIZE=100
|
|
105
|
+
|
|
106
|
+
# Maximum batch size for throughput (batches 11+)
|
|
107
|
+
# Larger = better throughput, but longer waits between updates
|
|
108
|
+
MAX_BATCH_SIZE=1000
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# CACHING
|
|
112
|
+
# =============================================================================
|
|
113
|
+
|
|
114
|
+
# How long to keep cached results (in days)
|
|
115
|
+
# Cached files are served instantly without reprocessing
|
|
116
|
+
CACHE_MAX_AGE_DAYS=7
|
|
117
|
+
`);
|
|
118
|
+
// Copy .env.example to .env
|
|
119
|
+
writeFileSync(join(targetDir, '.env'), `# IFC-Lite Server Configuration
|
|
120
|
+
# See .env.example for all available options with documentation
|
|
121
|
+
|
|
122
|
+
PORT=3001
|
|
123
|
+
RUST_LOG=info
|
|
124
|
+
MAX_FILE_SIZE_MB=500
|
|
125
|
+
WORKER_THREADS=4
|
|
126
|
+
`);
|
|
127
|
+
// .gitignore
|
|
128
|
+
writeFileSync(join(targetDir, '.gitignore'), `# Dependencies
|
|
129
|
+
node_modules/
|
|
130
|
+
|
|
131
|
+
# Build output
|
|
132
|
+
dist/
|
|
133
|
+
|
|
134
|
+
# Environment files (keep .env.example)
|
|
135
|
+
.env
|
|
136
|
+
.env.local
|
|
137
|
+
.env.*.local
|
|
138
|
+
|
|
139
|
+
# Cache directory (when using dev compose)
|
|
140
|
+
cache/
|
|
141
|
+
|
|
142
|
+
# IDE
|
|
143
|
+
.idea/
|
|
144
|
+
.vscode/
|
|
145
|
+
*.swp
|
|
146
|
+
*.swo
|
|
147
|
+
|
|
148
|
+
# OS
|
|
149
|
+
.DS_Store
|
|
150
|
+
Thumbs.db
|
|
151
|
+
|
|
152
|
+
# Logs
|
|
153
|
+
*.log
|
|
154
|
+
npm-debug.log*
|
|
155
|
+
`);
|
|
156
|
+
// .dockerignore
|
|
157
|
+
writeFileSync(join(targetDir, '.dockerignore'), `node_modules
|
|
158
|
+
dist
|
|
159
|
+
.git
|
|
160
|
+
.gitignore
|
|
161
|
+
*.md
|
|
162
|
+
.env
|
|
163
|
+
.env.*
|
|
164
|
+
cache
|
|
165
|
+
`);
|
|
166
|
+
// package.json
|
|
167
|
+
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
168
|
+
name: projectName,
|
|
169
|
+
version: '0.1.0',
|
|
170
|
+
type: 'module',
|
|
171
|
+
description: 'IFC processing server with TypeScript client',
|
|
172
|
+
scripts: {
|
|
173
|
+
'example': 'npx tsx src/example.ts',
|
|
174
|
+
'example:stream': 'npx tsx src/example-stream.ts',
|
|
175
|
+
'server:start': 'docker compose up -d',
|
|
176
|
+
'server:stop': 'docker compose down',
|
|
177
|
+
'server:logs': 'docker compose logs -f',
|
|
178
|
+
'server:dev': 'docker compose -f docker-compose.dev.yml up',
|
|
179
|
+
'build': 'tsc',
|
|
180
|
+
'typecheck': 'tsc --noEmit',
|
|
181
|
+
},
|
|
182
|
+
dependencies: {
|
|
183
|
+
'@ifc-lite/server-client': latestVersion,
|
|
184
|
+
},
|
|
185
|
+
devDependencies: {
|
|
186
|
+
'typescript': '^5.3.0',
|
|
187
|
+
'tsx': '^4.0.0',
|
|
188
|
+
'@types/node': '^20.0.0',
|
|
189
|
+
},
|
|
190
|
+
optionalDependencies: {
|
|
191
|
+
'parquet-wasm': '^0.6.0',
|
|
192
|
+
'apache-arrow': '^17.0.0',
|
|
193
|
+
},
|
|
194
|
+
}, null, 2));
|
|
195
|
+
// tsconfig.json
|
|
196
|
+
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
197
|
+
compilerOptions: {
|
|
198
|
+
target: 'ES2022',
|
|
199
|
+
module: 'ESNext',
|
|
200
|
+
moduleResolution: 'bundler',
|
|
201
|
+
strict: true,
|
|
202
|
+
esModuleInterop: true,
|
|
203
|
+
skipLibCheck: true,
|
|
204
|
+
outDir: 'dist',
|
|
205
|
+
declaration: true,
|
|
206
|
+
lib: ['ES2022'],
|
|
207
|
+
},
|
|
208
|
+
include: ['src'],
|
|
209
|
+
exclude: ['node_modules', 'dist'],
|
|
210
|
+
}, null, 2));
|
|
211
|
+
// Create src directory
|
|
212
|
+
mkdirSync(join(targetDir, 'src'));
|
|
213
|
+
// src/example.ts - Basic client example
|
|
214
|
+
writeFileSync(join(targetDir, 'src', 'example.ts'), `/**
|
|
215
|
+
* IFC-Lite Server Client Example
|
|
216
|
+
*
|
|
217
|
+
* This example demonstrates how to use the IFC-Lite server to parse IFC files.
|
|
218
|
+
* The server handles heavy geometry processing, caching, and streaming.
|
|
219
|
+
*
|
|
220
|
+
* Prerequisites:
|
|
221
|
+
* 1. Start the server: docker compose up -d
|
|
222
|
+
* 2. Run this example: npm run example
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
226
|
+
import { readFileSync, existsSync } from 'fs';
|
|
227
|
+
|
|
228
|
+
// Server URL - matches docker-compose.yml port mapping
|
|
229
|
+
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3001';
|
|
230
|
+
|
|
231
|
+
async function main() {
|
|
232
|
+
// Initialize client
|
|
233
|
+
const client = new IfcServerClient({
|
|
234
|
+
baseUrl: SERVER_URL,
|
|
235
|
+
timeout: 300000, // 5 minutes for large files
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Check server health
|
|
239
|
+
console.log('Checking server health...');
|
|
240
|
+
try {
|
|
241
|
+
const health = await client.health();
|
|
242
|
+
console.log(\`Server status: \${health.status}\`);
|
|
243
|
+
console.log(\`Server version: \${health.version || 'unknown'}\`);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('Failed to connect to server. Is it running?');
|
|
246
|
+
console.error('Start it with: docker compose up -d');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Get IFC file path from command line or use default
|
|
251
|
+
const ifcPath = process.argv[2];
|
|
252
|
+
|
|
253
|
+
if (!ifcPath) {
|
|
254
|
+
console.log(\`
|
|
255
|
+
Usage: npm run example <path-to-ifc-file>
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
npm run example ./model.ifc
|
|
259
|
+
|
|
260
|
+
The server will:
|
|
261
|
+
1. Check if the file is already cached (instant response)
|
|
262
|
+
2. If not cached, parse and process geometry
|
|
263
|
+
3. Cache the result for future requests
|
|
264
|
+
4. Return geometry data ready for rendering
|
|
265
|
+
\`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!existsSync(ifcPath)) {
|
|
270
|
+
console.error(\`File not found: \${ifcPath}\`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Read IFC file
|
|
275
|
+
console.log(\`\\nParsing: \${ifcPath}\`);
|
|
276
|
+
const buffer = readFileSync(ifcPath);
|
|
277
|
+
console.log(\`File size: \${(buffer.length / 1024 / 1024).toFixed(2)} MB\`);
|
|
278
|
+
|
|
279
|
+
// Parse with Parquet format (most efficient)
|
|
280
|
+
console.log('\\nSending to server...');
|
|
281
|
+
const startTime = performance.now();
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
// Check if Parquet is available (optional dependency)
|
|
285
|
+
const parquetAvailable = await client.isParquetSupported();
|
|
286
|
+
|
|
287
|
+
if (parquetAvailable) {
|
|
288
|
+
console.log('Using Parquet format (15x smaller than JSON)');
|
|
289
|
+
const result = await client.parseParquet(buffer);
|
|
290
|
+
|
|
291
|
+
const elapsed = performance.now() - startTime;
|
|
292
|
+
console.log(\`\\nParsing complete in \${elapsed.toFixed(0)}ms\`);
|
|
293
|
+
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
294
|
+
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
295
|
+
console.log(\` Payload size: \${(result.parquet_stats.payload_size / 1024).toFixed(1)} KB\`);
|
|
296
|
+
console.log(\` Decode time: \${result.parquet_stats.decode_time_ms}ms\`);
|
|
297
|
+
|
|
298
|
+
if (result.stats) {
|
|
299
|
+
console.log(\`\\nServer stats:\`);
|
|
300
|
+
console.log(\` Parse time: \${result.stats.parse_time_ms}ms\`);
|
|
301
|
+
console.log(\` Geometry time: \${result.stats.geometry_time_ms}ms\`);
|
|
302
|
+
console.log(\` Total triangles: \${result.stats.total_triangles}\`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Show sample mesh data
|
|
306
|
+
if (result.meshes.length > 0) {
|
|
307
|
+
const mesh = result.meshes[0];
|
|
308
|
+
console.log(\`\\nSample mesh:\`);
|
|
309
|
+
console.log(\` Express ID: \${mesh.express_id}\`);
|
|
310
|
+
console.log(\` Vertices: \${mesh.positions.length / 3}\`);
|
|
311
|
+
console.log(\` Triangles: \${mesh.indices.length / 3}\`);
|
|
312
|
+
console.log(\` Color: rgba(\${mesh.color.join(', ')})\`);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// Fallback to JSON format
|
|
316
|
+
console.log('Parquet not available, using JSON format');
|
|
317
|
+
console.log('Install optional deps for smaller payloads: npm install parquet-wasm apache-arrow');
|
|
318
|
+
|
|
319
|
+
const result = await client.parse(buffer);
|
|
320
|
+
|
|
321
|
+
const elapsed = performance.now() - startTime;
|
|
322
|
+
console.log(\`\\nParsing complete in \${elapsed.toFixed(0)}ms\`);
|
|
323
|
+
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
324
|
+
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Parse failed:', error);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
main().catch(console.error);
|
|
333
|
+
`);
|
|
334
|
+
// src/example-stream.ts - Streaming example for large files
|
|
335
|
+
writeFileSync(join(targetDir, 'src', 'example-stream.ts'), `/**
|
|
336
|
+
* IFC-Lite Server Streaming Example
|
|
337
|
+
*
|
|
338
|
+
* This example demonstrates streaming parsing for large IFC files.
|
|
339
|
+
* Geometry batches are received progressively, enabling immediate rendering
|
|
340
|
+
* while the server continues processing.
|
|
341
|
+
*
|
|
342
|
+
* Best for: Files > 50MB where you want progressive rendering
|
|
343
|
+
*
|
|
344
|
+
* Prerequisites:
|
|
345
|
+
* 1. Start the server: docker compose up -d
|
|
346
|
+
* 2. Install optional deps: npm install parquet-wasm apache-arrow
|
|
347
|
+
* 3. Run: npm run example:stream <path-to-ifc>
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
351
|
+
import { readFileSync, existsSync } from 'fs';
|
|
352
|
+
|
|
353
|
+
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3001';
|
|
354
|
+
|
|
355
|
+
async function main() {
|
|
356
|
+
const client = new IfcServerClient({
|
|
357
|
+
baseUrl: SERVER_URL,
|
|
358
|
+
timeout: 600000, // 10 minutes for very large files
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Check server
|
|
362
|
+
try {
|
|
363
|
+
await client.health();
|
|
364
|
+
console.log('Server connected');
|
|
365
|
+
} catch {
|
|
366
|
+
console.error('Server not available. Start with: docker compose up -d');
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const ifcPath = process.argv[2];
|
|
371
|
+
if (!ifcPath || !existsSync(ifcPath)) {
|
|
372
|
+
console.log('Usage: npm run example:stream <path-to-ifc-file>');
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const buffer = readFileSync(ifcPath);
|
|
377
|
+
console.log(\`\\nStreaming: \${ifcPath} (\${(buffer.length / 1024 / 1024).toFixed(1)} MB)\`);
|
|
378
|
+
|
|
379
|
+
// Check Parquet support
|
|
380
|
+
const parquetAvailable = await client.isParquetSupported();
|
|
381
|
+
if (!parquetAvailable) {
|
|
382
|
+
console.error('Streaming requires parquet-wasm and apache-arrow.');
|
|
383
|
+
console.error('Install with: npm install parquet-wasm apache-arrow');
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const startTime = performance.now();
|
|
388
|
+
let totalMeshes = 0;
|
|
389
|
+
let batchCount = 0;
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
// Stream with batch callback
|
|
393
|
+
const result = await client.parseParquetStream(buffer, (batch) => {
|
|
394
|
+
batchCount++;
|
|
395
|
+
totalMeshes += batch.meshes.length;
|
|
396
|
+
|
|
397
|
+
// In a real app, you would render each batch immediately
|
|
398
|
+
console.log(
|
|
399
|
+
\` Batch #\${batch.batch_number}: +\${batch.meshes.length} meshes \` +
|
|
400
|
+
\`(decode: \${batch.decode_time_ms.toFixed(0)}ms) - Total: \${totalMeshes}\`
|
|
401
|
+
);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const elapsed = performance.now() - startTime;
|
|
405
|
+
console.log(\`\\nStreaming complete!\`);
|
|
406
|
+
console.log(\` Total time: \${elapsed.toFixed(0)}ms\`);
|
|
407
|
+
console.log(\` Batches received: \${batchCount}\`);
|
|
408
|
+
console.log(\` Total meshes: \${result.total_meshes}\`);
|
|
409
|
+
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
410
|
+
|
|
411
|
+
if (result.stats) {
|
|
412
|
+
console.log(\`\\nServer processing:\`);
|
|
413
|
+
console.log(\` Parse: \${result.stats.parse_time_ms}ms\`);
|
|
414
|
+
console.log(\` Geometry: \${result.stats.geometry_time_ms}ms\`);
|
|
415
|
+
console.log(\` Triangles: \${result.stats.total_triangles}\`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Optionally fetch full data model for properties panel
|
|
419
|
+
console.log(\`\\nFetching data model for properties...\`);
|
|
420
|
+
const dataModel = await client.fetchDataModel(result.cache_key);
|
|
421
|
+
if (dataModel) {
|
|
422
|
+
console.log(\` Data model size: \${(dataModel.byteLength / 1024).toFixed(1)} KB\`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error('Streaming failed:', error);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
main().catch(console.error);
|
|
432
|
+
`);
|
|
433
|
+
// src/index.ts - Library re-export for custom integrations
|
|
434
|
+
writeFileSync(join(targetDir, 'src', 'index.ts'), `/**
|
|
435
|
+
* ${projectName} - IFC Processing Server Client
|
|
436
|
+
*
|
|
437
|
+
* Re-exports the IFC-Lite server client for custom integrations.
|
|
438
|
+
* See example.ts and example-stream.ts for usage examples.
|
|
439
|
+
*/
|
|
440
|
+
|
|
441
|
+
export { IfcServerClient } from '@ifc-lite/server-client';
|
|
442
|
+
export type {
|
|
443
|
+
ServerConfig,
|
|
444
|
+
ParseResponse,
|
|
445
|
+
ParquetParseResponse,
|
|
446
|
+
StreamEvent,
|
|
447
|
+
HealthResponse,
|
|
448
|
+
MetadataResponse,
|
|
449
|
+
} from '@ifc-lite/server-client';
|
|
450
|
+
|
|
451
|
+
// Default server URL
|
|
452
|
+
export const DEFAULT_SERVER_URL = 'http://localhost:3001';
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Create a pre-configured client for the local Docker server.
|
|
456
|
+
*/
|
|
457
|
+
export function createLocalClient(options?: { timeout?: number }) {
|
|
458
|
+
const { IfcServerClient } = require('@ifc-lite/server-client');
|
|
459
|
+
return new IfcServerClient({
|
|
460
|
+
baseUrl: process.env.SERVER_URL || DEFAULT_SERVER_URL,
|
|
461
|
+
timeout: options?.timeout ?? 300000,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
`);
|
|
465
|
+
// README.md
|
|
466
|
+
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
467
|
+
|
|
468
|
+
IFC processing server using [IFC-Lite](https://github.com/louistrue/ifc-lite).
|
|
469
|
+
|
|
470
|
+
## Quick Start
|
|
471
|
+
|
|
472
|
+
\`\`\`bash
|
|
473
|
+
# 1. Start the server
|
|
474
|
+
docker compose up -d
|
|
475
|
+
|
|
476
|
+
# 2. Install client dependencies
|
|
477
|
+
npm install
|
|
478
|
+
|
|
479
|
+
# 3. Run the example (replace with your IFC file)
|
|
480
|
+
npm run example ./your-model.ifc
|
|
481
|
+
\`\`\`
|
|
482
|
+
|
|
483
|
+
## Features
|
|
484
|
+
|
|
485
|
+
| Feature | Description |
|
|
486
|
+
|---------|-------------|
|
|
487
|
+
| **Content-Addressable Cache** | Same file = instant response (no reprocessing) |
|
|
488
|
+
| **Parquet Format** | 15x smaller payloads than JSON |
|
|
489
|
+
| **Streaming** | Progressive geometry for large files |
|
|
490
|
+
| **Parallel Processing** | Multi-threaded geometry processing |
|
|
491
|
+
|
|
492
|
+
## Architecture
|
|
493
|
+
|
|
494
|
+
\`\`\`
|
|
495
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
496
|
+
│ Your App │────▶│ IFC-Lite Server │────▶│ Cache │
|
|
497
|
+
│ (TypeScript) │◀────│ (Docker) │◀────│ (Volume) │
|
|
498
|
+
└─────────────────┘ └──────────────────┘ └─────────────┘
|
|
499
|
+
\`\`\`
|
|
500
|
+
|
|
501
|
+
## Scripts
|
|
502
|
+
|
|
503
|
+
| Command | Description |
|
|
504
|
+
|---------|-------------|
|
|
505
|
+
| \`npm run example\` | Basic parsing example |
|
|
506
|
+
| \`npm run example:stream\` | Streaming example for large files |
|
|
507
|
+
| \`npm run server:start\` | Start server in background |
|
|
508
|
+
| \`npm run server:stop\` | Stop server |
|
|
509
|
+
| \`npm run server:logs\` | View server logs |
|
|
510
|
+
| \`npm run server:dev\` | Start with debug logging |
|
|
511
|
+
|
|
512
|
+
## Configuration
|
|
513
|
+
|
|
514
|
+
Copy \`.env.example\` to \`.env\` to customize:
|
|
515
|
+
|
|
516
|
+
| Variable | Default | Description |
|
|
517
|
+
|----------|---------|-------------|
|
|
518
|
+
| \`PORT\` | 3001 | Server port |
|
|
519
|
+
| \`MAX_FILE_SIZE_MB\` | 500 | Max upload size |
|
|
520
|
+
| \`WORKER_THREADS\` | 4 | Parallel processing threads |
|
|
521
|
+
| \`CACHE_MAX_AGE_DAYS\` | 7 | Cache retention |
|
|
522
|
+
|
|
523
|
+
See \`.env.example\` for all options with documentation.
|
|
524
|
+
|
|
525
|
+
## API Endpoints
|
|
526
|
+
|
|
527
|
+
| Endpoint | Description |
|
|
528
|
+
|----------|-------------|
|
|
529
|
+
| \`GET /api/v1/health\` | Health check |
|
|
530
|
+
| \`POST /api/v1/parse\` | Full parse (JSON response) |
|
|
531
|
+
| \`POST /api/v1/parse/parquet\` | Full parse (Parquet, 15x smaller) |
|
|
532
|
+
| \`POST /api/v1/parse/parquet-stream\` | Streaming parse (SSE + Parquet) |
|
|
533
|
+
| \`GET /api/v1/cache/check/:hash\` | Check if file is cached |
|
|
534
|
+
|
|
535
|
+
## Client Usage
|
|
536
|
+
|
|
537
|
+
### Basic Parse
|
|
538
|
+
|
|
539
|
+
\`\`\`typescript
|
|
540
|
+
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
541
|
+
|
|
542
|
+
const client = new IfcServerClient({
|
|
543
|
+
baseUrl: 'http://localhost:3001'
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const result = await client.parseParquet(ifcBuffer);
|
|
547
|
+
console.log(\`Meshes: \${result.meshes.length}\`);
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
### Streaming (Large Files)
|
|
551
|
+
|
|
552
|
+
\`\`\`typescript
|
|
553
|
+
await client.parseParquetStream(ifcBuffer, (batch) => {
|
|
554
|
+
// Render each batch immediately
|
|
555
|
+
for (const mesh of batch.meshes) {
|
|
556
|
+
scene.addMesh(mesh);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
\`\`\`
|
|
560
|
+
|
|
561
|
+
### Cache-First Pattern
|
|
562
|
+
|
|
563
|
+
\`\`\`typescript
|
|
564
|
+
// Client automatically:
|
|
565
|
+
// 1. Computes file hash locally
|
|
566
|
+
// 2. Checks server cache
|
|
567
|
+
// 3. Skips upload if cached (instant response!)
|
|
568
|
+
const result = await client.parseParquet(ifcBuffer);
|
|
569
|
+
\`\`\`
|
|
570
|
+
|
|
571
|
+
## Production Deployment
|
|
572
|
+
|
|
573
|
+
### Railway / Render / Fly.io
|
|
574
|
+
|
|
575
|
+
The Docker image works on any container platform:
|
|
576
|
+
|
|
577
|
+
\`\`\`bash
|
|
578
|
+
# Pull and run
|
|
579
|
+
docker pull ghcr.io/louistrue/ifc-lite-server:latest
|
|
580
|
+
docker run -p 8080:8080 -v ifc-cache:/app/cache ghcr.io/louistrue/ifc-lite-server
|
|
581
|
+
\`\`\`
|
|
582
|
+
|
|
583
|
+
### Environment Variables
|
|
584
|
+
|
|
585
|
+
Set these in your deployment platform:
|
|
586
|
+
|
|
587
|
+
\`\`\`
|
|
588
|
+
PORT=8080
|
|
589
|
+
MAX_FILE_SIZE_MB=500
|
|
590
|
+
WORKER_THREADS=4
|
|
591
|
+
CACHE_MAX_AGE_DAYS=30
|
|
592
|
+
RUST_LOG=info
|
|
593
|
+
\`\`\`
|
|
594
|
+
|
|
595
|
+
## Learn More
|
|
596
|
+
|
|
597
|
+
- [IFC-Lite Documentation](https://louistrue.github.io/ifc-lite/)
|
|
598
|
+
- [Server API Reference](https://louistrue.github.io/ifc-lite/api/server/)
|
|
599
|
+
- [GitHub Repository](https://github.com/louistrue/ifc-lite)
|
|
600
|
+
`);
|
|
601
|
+
console.log(' Created docker-compose.yml');
|
|
602
|
+
console.log(' Created docker-compose.dev.yml');
|
|
603
|
+
console.log(' Created .env.example');
|
|
604
|
+
console.log(' Created .env');
|
|
605
|
+
console.log(' Created package.json');
|
|
606
|
+
console.log(' Created tsconfig.json');
|
|
607
|
+
console.log(' Created src/example.ts');
|
|
608
|
+
console.log(' Created src/example-stream.ts');
|
|
609
|
+
console.log(' Created src/index.ts');
|
|
610
|
+
console.log(' Created README.md');
|
|
611
|
+
console.log(' Created .gitignore');
|
|
612
|
+
console.log(' Created .dockerignore');
|
|
613
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch the latest published version of @ifc-lite/parser from npm.
|
|
3
|
+
* Falls back to '^1.0.0' when the registry is unreachable.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getLatestVersion(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Rewrite the viewer's package.json so it works as a standalone project:
|
|
8
|
+
* - Set the project name
|
|
9
|
+
* - Replace workspace: protocol versions with the latest npm version
|
|
10
|
+
* - Remove the .git directory if present
|
|
11
|
+
*/
|
|
12
|
+
export declare function fixPackageJson(targetDir: string, projectName: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Write a standalone tsconfig.json without monorepo references.
|
|
15
|
+
*/
|
|
16
|
+
export declare function fixTsConfig(targetDir: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Write a standalone vite.config.ts with WASM support.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fixViteConfig(targetDir: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Apply all viewer-template fixups: package.json, tsconfig, vite config.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fixViewerTemplate(targetDir: string, projectName: string): void;
|