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
package/dist/index.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
5
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { createBasicTemplate } from './templates/basic.js';
|
|
8
|
+
import { createServerTemplate } from './templates/server.js';
|
|
9
|
+
import { createServerNativeTemplate } from './templates/server-native.js';
|
|
10
|
+
import { fixViewerTemplate } from './utils/config-fixers.js';
|
|
11
|
+
import { downloadViewer } from './utils/download.js';
|
|
7
12
|
const TEMPLATES = {
|
|
8
13
|
basic: 'basic',
|
|
9
14
|
react: 'react',
|
|
10
15
|
server: 'server',
|
|
11
16
|
'server-native': 'server-native',
|
|
12
17
|
};
|
|
13
|
-
const REPO_URL = 'https://github.com/louistrue/ifc-lite';
|
|
14
|
-
const VIEWER_PATH = 'apps/viewer';
|
|
15
|
-
function getLatestVersion() {
|
|
16
|
-
try {
|
|
17
|
-
const result = execSync('npm view @ifc-lite/parser version', { stdio: 'pipe' });
|
|
18
|
-
return `^${result.toString().trim()}`;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return '^1.0.0'; // fallback
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
18
|
function printUsage() {
|
|
25
19
|
console.log(`
|
|
26
20
|
create-ifc-lite - Create IFC-Lite projects instantly
|
|
@@ -45,153 +39,6 @@ function printUsage() {
|
|
|
45
39
|
server-native Native binary server (no Docker required)
|
|
46
40
|
`);
|
|
47
41
|
}
|
|
48
|
-
function runCommand(cmd, cwd) {
|
|
49
|
-
try {
|
|
50
|
-
execSync(cmd, { cwd, stdio: 'pipe' });
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
async function downloadViewer(targetDir, projectName) {
|
|
58
|
-
// Try degit first (fastest)
|
|
59
|
-
if (runCommand('npx --version')) {
|
|
60
|
-
console.log(' Downloading viewer template...');
|
|
61
|
-
try {
|
|
62
|
-
execSync(`npx degit ${REPO_URL}/${VIEWER_PATH} "${targetDir}"`, {
|
|
63
|
-
stdio: 'pipe',
|
|
64
|
-
timeout: 60000
|
|
65
|
-
});
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// degit failed, try git sparse checkout
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// Fallback: git sparse checkout
|
|
73
|
-
if (runCommand('git --version')) {
|
|
74
|
-
console.log(' Downloading via git...');
|
|
75
|
-
const tempDir = join(dirname(targetDir), `.temp-${Date.now()}`);
|
|
76
|
-
try {
|
|
77
|
-
execSync(`git clone --filter=blob:none --sparse "${REPO_URL}.git" "${tempDir}"`, {
|
|
78
|
-
stdio: 'pipe',
|
|
79
|
-
timeout: 120000
|
|
80
|
-
});
|
|
81
|
-
execSync(`git sparse-checkout set ${VIEWER_PATH}`, { cwd: tempDir, stdio: 'pipe' });
|
|
82
|
-
// Move viewer to target
|
|
83
|
-
const viewerSrc = join(tempDir, VIEWER_PATH);
|
|
84
|
-
execSync(`mv "${viewerSrc}" "${targetDir}"`, { stdio: 'pipe' });
|
|
85
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
function fixPackageJson(targetDir, projectName) {
|
|
95
|
-
const pkgPath = join(targetDir, 'package.json');
|
|
96
|
-
if (!existsSync(pkgPath))
|
|
97
|
-
return;
|
|
98
|
-
let pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
99
|
-
// Update name
|
|
100
|
-
pkg.name = projectName;
|
|
101
|
-
// Replace workspace protocol with latest npm version in all dependency fields
|
|
102
|
-
const latestVersion = getLatestVersion();
|
|
103
|
-
const depFields = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
104
|
-
for (const field of depFields) {
|
|
105
|
-
const deps = pkg[field];
|
|
106
|
-
if (!deps)
|
|
107
|
-
continue;
|
|
108
|
-
for (const [name, version] of Object.entries(deps)) {
|
|
109
|
-
if (typeof version === 'string' && version.includes('workspace:')) {
|
|
110
|
-
deps[name] = latestVersion;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// Remove git directory if present
|
|
115
|
-
const gitDir = join(targetDir, '.git');
|
|
116
|
-
if (existsSync(gitDir)) {
|
|
117
|
-
rmSync(gitDir, { recursive: true, force: true });
|
|
118
|
-
}
|
|
119
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
120
|
-
}
|
|
121
|
-
function fixTsConfig(targetDir) {
|
|
122
|
-
const tsconfigPath = join(targetDir, 'tsconfig.json');
|
|
123
|
-
// Write standalone tsconfig without monorepo references
|
|
124
|
-
const tsconfig = {
|
|
125
|
-
compilerOptions: {
|
|
126
|
-
target: 'ES2022',
|
|
127
|
-
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
128
|
-
module: 'ESNext',
|
|
129
|
-
moduleResolution: 'bundler',
|
|
130
|
-
jsx: 'react-jsx',
|
|
131
|
-
strict: true,
|
|
132
|
-
esModuleInterop: true,
|
|
133
|
-
skipLibCheck: true,
|
|
134
|
-
noEmit: true,
|
|
135
|
-
baseUrl: '.',
|
|
136
|
-
paths: {
|
|
137
|
-
'@/*': ['./src/*']
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
include: ['src/**/*'],
|
|
141
|
-
exclude: ['node_modules']
|
|
142
|
-
};
|
|
143
|
-
writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
144
|
-
}
|
|
145
|
-
function fixViteConfig(targetDir) {
|
|
146
|
-
const viteConfigPath = join(targetDir, 'vite.config.ts');
|
|
147
|
-
// Write standalone vite config with WASM support
|
|
148
|
-
const viteConfig = `import { defineConfig } from 'vite';
|
|
149
|
-
import react from '@vitejs/plugin-react';
|
|
150
|
-
import path from 'path';
|
|
151
|
-
|
|
152
|
-
export default defineConfig({
|
|
153
|
-
plugins: [
|
|
154
|
-
react(),
|
|
155
|
-
{
|
|
156
|
-
name: 'wasm-mime-type',
|
|
157
|
-
configureServer(server) {
|
|
158
|
-
server.middlewares.use((req, res, next) => {
|
|
159
|
-
if (req.url?.endsWith('.wasm')) {
|
|
160
|
-
res.setHeader('Content-Type', 'application/wasm');
|
|
161
|
-
}
|
|
162
|
-
next();
|
|
163
|
-
});
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
],
|
|
167
|
-
resolve: {
|
|
168
|
-
alias: {
|
|
169
|
-
'@': path.resolve(__dirname, './src'),
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
server: {
|
|
173
|
-
port: 3000,
|
|
174
|
-
open: true,
|
|
175
|
-
fs: {
|
|
176
|
-
allow: ['..'],
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
build: {
|
|
180
|
-
target: 'esnext',
|
|
181
|
-
},
|
|
182
|
-
optimizeDeps: {
|
|
183
|
-
exclude: ['@duckdb/duckdb-wasm', '@ifc-lite/wasm'],
|
|
184
|
-
},
|
|
185
|
-
assetsInclude: ['**/*.wasm'],
|
|
186
|
-
});
|
|
187
|
-
`;
|
|
188
|
-
writeFileSync(viteConfigPath, viteConfig);
|
|
189
|
-
}
|
|
190
|
-
function fixViewerTemplate(targetDir, projectName) {
|
|
191
|
-
fixPackageJson(targetDir, projectName);
|
|
192
|
-
fixTsConfig(targetDir);
|
|
193
|
-
fixViteConfig(targetDir);
|
|
194
|
-
}
|
|
195
42
|
async function main() {
|
|
196
43
|
const args = process.argv.slice(2);
|
|
197
44
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -270,1091 +117,4 @@ async function main() {
|
|
|
270
117
|
}
|
|
271
118
|
console.log();
|
|
272
119
|
}
|
|
273
|
-
function createBasicTemplate(targetDir, projectName) {
|
|
274
|
-
const latestVersion = getLatestVersion();
|
|
275
|
-
// package.json
|
|
276
|
-
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
277
|
-
name: projectName,
|
|
278
|
-
version: latestVersion.replace('^', ''),
|
|
279
|
-
type: 'module',
|
|
280
|
-
scripts: {
|
|
281
|
-
parse: 'npx tsx src/index.ts',
|
|
282
|
-
build: 'tsc',
|
|
283
|
-
},
|
|
284
|
-
dependencies: {
|
|
285
|
-
'@ifc-lite/parser': latestVersion,
|
|
286
|
-
},
|
|
287
|
-
devDependencies: {
|
|
288
|
-
typescript: '^5.3.0',
|
|
289
|
-
tsx: '^4.0.0',
|
|
290
|
-
},
|
|
291
|
-
}, null, 2));
|
|
292
|
-
// tsconfig.json
|
|
293
|
-
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
294
|
-
compilerOptions: {
|
|
295
|
-
target: 'ES2022',
|
|
296
|
-
module: 'ESNext',
|
|
297
|
-
moduleResolution: 'bundler',
|
|
298
|
-
strict: true,
|
|
299
|
-
esModuleInterop: true,
|
|
300
|
-
skipLibCheck: true,
|
|
301
|
-
outDir: 'dist',
|
|
302
|
-
},
|
|
303
|
-
include: ['src'],
|
|
304
|
-
}, null, 2));
|
|
305
|
-
// src/index.ts
|
|
306
|
-
mkdirSync(join(targetDir, 'src'));
|
|
307
|
-
writeFileSync(join(targetDir, 'src', 'index.ts'), `import { IfcParser } from '@ifc-lite/parser';
|
|
308
|
-
import { readFileSync } from 'fs';
|
|
309
|
-
|
|
310
|
-
// Example: Parse an IFC file
|
|
311
|
-
const ifcPath = process.argv[2];
|
|
312
|
-
|
|
313
|
-
if (!ifcPath) {
|
|
314
|
-
console.log('Usage: npm run parse <path-to-ifc-file>');
|
|
315
|
-
console.log('');
|
|
316
|
-
console.log('Example:');
|
|
317
|
-
console.log(' npm run parse ./model.ifc');
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const buffer = readFileSync(ifcPath);
|
|
322
|
-
const parser = new IfcParser();
|
|
323
|
-
|
|
324
|
-
console.log('Parsing IFC file...');
|
|
325
|
-
parser.parse(buffer).then(result => {
|
|
326
|
-
console.log('\\nFile parsed successfully!');
|
|
327
|
-
console.log(\` Entities: \${result.entityCount}\`);
|
|
328
|
-
|
|
329
|
-
// Count by type
|
|
330
|
-
const typeCounts = new Map<string, number>();
|
|
331
|
-
for (const [id, entity] of result.entities) {
|
|
332
|
-
typeCounts.set(entity.type, (typeCounts.get(entity.type) || 0) + 1);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
console.log('\\nTop entity types:');
|
|
336
|
-
const sorted = [...typeCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
337
|
-
for (const [type, count] of sorted) {
|
|
338
|
-
console.log(\` \${type}: \${count}\`);
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
`);
|
|
342
|
-
// README
|
|
343
|
-
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
344
|
-
|
|
345
|
-
IFC parser project using [IFC-Lite](https://github.com/louistrue/ifc-lite).
|
|
346
|
-
|
|
347
|
-
## Quick Start
|
|
348
|
-
|
|
349
|
-
\`\`\`bash
|
|
350
|
-
npm install
|
|
351
|
-
npm run parse ./your-model.ifc
|
|
352
|
-
\`\`\`
|
|
353
|
-
|
|
354
|
-
## Learn More
|
|
355
|
-
|
|
356
|
-
- [IFC-Lite Documentation](https://louistrue.github.io/ifc-lite/)
|
|
357
|
-
- [API Reference](https://louistrue.github.io/ifc-lite/api/)
|
|
358
|
-
`);
|
|
359
|
-
}
|
|
360
|
-
function createServerTemplate(targetDir, projectName) {
|
|
361
|
-
const latestVersion = getLatestVersion();
|
|
362
|
-
// docker-compose.yml - Production configuration
|
|
363
|
-
writeFileSync(join(targetDir, 'docker-compose.yml'), `# IFC-Lite Server - Production Configuration
|
|
364
|
-
# Start with: docker compose up -d
|
|
365
|
-
|
|
366
|
-
services:
|
|
367
|
-
ifc-server:
|
|
368
|
-
image: ghcr.io/louistrue/ifc-lite-server:latest
|
|
369
|
-
container_name: ${projectName}-server
|
|
370
|
-
ports:
|
|
371
|
-
- "\${PORT:-3001}:8080"
|
|
372
|
-
volumes:
|
|
373
|
-
- ifc-cache:/app/cache
|
|
374
|
-
environment:
|
|
375
|
-
- RUST_LOG=\${RUST_LOG:-info}
|
|
376
|
-
- MAX_FILE_SIZE_MB=\${MAX_FILE_SIZE_MB:-500}
|
|
377
|
-
- REQUEST_TIMEOUT_SECS=\${REQUEST_TIMEOUT_SECS:-300}
|
|
378
|
-
- WORKER_THREADS=\${WORKER_THREADS:-4}
|
|
379
|
-
- INITIAL_BATCH_SIZE=\${INITIAL_BATCH_SIZE:-100}
|
|
380
|
-
- MAX_BATCH_SIZE=\${MAX_BATCH_SIZE:-1000}
|
|
381
|
-
- CACHE_MAX_AGE_DAYS=\${CACHE_MAX_AGE_DAYS:-7}
|
|
382
|
-
healthcheck:
|
|
383
|
-
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
|
|
384
|
-
interval: 30s
|
|
385
|
-
timeout: 3s
|
|
386
|
-
retries: 3
|
|
387
|
-
start_period: 5s
|
|
388
|
-
restart: unless-stopped
|
|
389
|
-
|
|
390
|
-
volumes:
|
|
391
|
-
ifc-cache:
|
|
392
|
-
name: ${projectName}-cache
|
|
393
|
-
`);
|
|
394
|
-
// docker-compose.dev.yml - Development configuration with live logs
|
|
395
|
-
writeFileSync(join(targetDir, 'docker-compose.dev.yml'), `# IFC-Lite Server - Development Configuration
|
|
396
|
-
# Start with: docker compose -f docker-compose.dev.yml up
|
|
397
|
-
|
|
398
|
-
services:
|
|
399
|
-
ifc-server:
|
|
400
|
-
image: ghcr.io/louistrue/ifc-lite-server:latest
|
|
401
|
-
container_name: ${projectName}-server-dev
|
|
402
|
-
ports:
|
|
403
|
-
- "\${PORT:-3001}:8080"
|
|
404
|
-
volumes:
|
|
405
|
-
- ./cache:/app/cache # Local cache directory for inspection
|
|
406
|
-
environment:
|
|
407
|
-
- RUST_LOG=debug,tower_http=debug,ifc_lite_server=debug
|
|
408
|
-
- MAX_FILE_SIZE_MB=\${MAX_FILE_SIZE_MB:-500}
|
|
409
|
-
- REQUEST_TIMEOUT_SECS=\${REQUEST_TIMEOUT_SECS:-300}
|
|
410
|
-
- WORKER_THREADS=\${WORKER_THREADS:-4}
|
|
411
|
-
- INITIAL_BATCH_SIZE=\${INITIAL_BATCH_SIZE:-100}
|
|
412
|
-
- MAX_BATCH_SIZE=\${MAX_BATCH_SIZE:-1000}
|
|
413
|
-
- CACHE_MAX_AGE_DAYS=\${CACHE_MAX_AGE_DAYS:-30}
|
|
414
|
-
# No restart in dev - easier to debug
|
|
415
|
-
`);
|
|
416
|
-
// .env.example - Documented environment variables
|
|
417
|
-
writeFileSync(join(targetDir, '.env.example'), `# IFC-Lite Server Configuration
|
|
418
|
-
# Copy this file to .env and modify as needed
|
|
419
|
-
|
|
420
|
-
# =============================================================================
|
|
421
|
-
# SERVER SETTINGS
|
|
422
|
-
# =============================================================================
|
|
423
|
-
|
|
424
|
-
# Port to expose the server on (maps to internal port 8080)
|
|
425
|
-
PORT=3001
|
|
426
|
-
|
|
427
|
-
# Log level: error, warn, info, debug, trace
|
|
428
|
-
# Use "debug" for development, "info" for production
|
|
429
|
-
RUST_LOG=info
|
|
430
|
-
|
|
431
|
-
# =============================================================================
|
|
432
|
-
# FILE PROCESSING
|
|
433
|
-
# =============================================================================
|
|
434
|
-
|
|
435
|
-
# Maximum IFC file size in megabytes
|
|
436
|
-
# Larger files need more memory and processing time
|
|
437
|
-
MAX_FILE_SIZE_MB=500
|
|
438
|
-
|
|
439
|
-
# Request timeout in seconds
|
|
440
|
-
# Increase for very large files (500MB+)
|
|
441
|
-
REQUEST_TIMEOUT_SECS=300
|
|
442
|
-
|
|
443
|
-
# Number of worker threads for parallel geometry processing
|
|
444
|
-
# Default: number of CPU cores
|
|
445
|
-
# Reduce if running alongside other services
|
|
446
|
-
WORKER_THREADS=4
|
|
447
|
-
|
|
448
|
-
# =============================================================================
|
|
449
|
-
# STREAMING (Progressive Rendering)
|
|
450
|
-
# =============================================================================
|
|
451
|
-
|
|
452
|
-
# Initial batch size for fast first frame (first 3 batches)
|
|
453
|
-
# Smaller = faster first render, but more HTTP overhead
|
|
454
|
-
INITIAL_BATCH_SIZE=100
|
|
455
|
-
|
|
456
|
-
# Maximum batch size for throughput (batches 11+)
|
|
457
|
-
# Larger = better throughput, but longer waits between updates
|
|
458
|
-
MAX_BATCH_SIZE=1000
|
|
459
|
-
|
|
460
|
-
# =============================================================================
|
|
461
|
-
# CACHING
|
|
462
|
-
# =============================================================================
|
|
463
|
-
|
|
464
|
-
# How long to keep cached results (in days)
|
|
465
|
-
# Cached files are served instantly without reprocessing
|
|
466
|
-
CACHE_MAX_AGE_DAYS=7
|
|
467
|
-
`);
|
|
468
|
-
// Copy .env.example to .env
|
|
469
|
-
writeFileSync(join(targetDir, '.env'), `# IFC-Lite Server Configuration
|
|
470
|
-
# See .env.example for all available options with documentation
|
|
471
|
-
|
|
472
|
-
PORT=3001
|
|
473
|
-
RUST_LOG=info
|
|
474
|
-
MAX_FILE_SIZE_MB=500
|
|
475
|
-
WORKER_THREADS=4
|
|
476
|
-
`);
|
|
477
|
-
// .gitignore
|
|
478
|
-
writeFileSync(join(targetDir, '.gitignore'), `# Dependencies
|
|
479
|
-
node_modules/
|
|
480
|
-
|
|
481
|
-
# Build output
|
|
482
|
-
dist/
|
|
483
|
-
|
|
484
|
-
# Environment files (keep .env.example)
|
|
485
|
-
.env
|
|
486
|
-
.env.local
|
|
487
|
-
.env.*.local
|
|
488
|
-
|
|
489
|
-
# Cache directory (when using dev compose)
|
|
490
|
-
cache/
|
|
491
|
-
|
|
492
|
-
# IDE
|
|
493
|
-
.idea/
|
|
494
|
-
.vscode/
|
|
495
|
-
*.swp
|
|
496
|
-
*.swo
|
|
497
|
-
|
|
498
|
-
# OS
|
|
499
|
-
.DS_Store
|
|
500
|
-
Thumbs.db
|
|
501
|
-
|
|
502
|
-
# Logs
|
|
503
|
-
*.log
|
|
504
|
-
npm-debug.log*
|
|
505
|
-
`);
|
|
506
|
-
// .dockerignore
|
|
507
|
-
writeFileSync(join(targetDir, '.dockerignore'), `node_modules
|
|
508
|
-
dist
|
|
509
|
-
.git
|
|
510
|
-
.gitignore
|
|
511
|
-
*.md
|
|
512
|
-
.env
|
|
513
|
-
.env.*
|
|
514
|
-
cache
|
|
515
|
-
`);
|
|
516
|
-
// package.json
|
|
517
|
-
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
518
|
-
name: projectName,
|
|
519
|
-
version: '0.1.0',
|
|
520
|
-
type: 'module',
|
|
521
|
-
description: 'IFC processing server with TypeScript client',
|
|
522
|
-
scripts: {
|
|
523
|
-
'example': 'npx tsx src/example.ts',
|
|
524
|
-
'example:stream': 'npx tsx src/example-stream.ts',
|
|
525
|
-
'server:start': 'docker compose up -d',
|
|
526
|
-
'server:stop': 'docker compose down',
|
|
527
|
-
'server:logs': 'docker compose logs -f',
|
|
528
|
-
'server:dev': 'docker compose -f docker-compose.dev.yml up',
|
|
529
|
-
'build': 'tsc',
|
|
530
|
-
'typecheck': 'tsc --noEmit',
|
|
531
|
-
},
|
|
532
|
-
dependencies: {
|
|
533
|
-
'@ifc-lite/server-client': latestVersion,
|
|
534
|
-
},
|
|
535
|
-
devDependencies: {
|
|
536
|
-
'typescript': '^5.3.0',
|
|
537
|
-
'tsx': '^4.0.0',
|
|
538
|
-
'@types/node': '^20.0.0',
|
|
539
|
-
},
|
|
540
|
-
optionalDependencies: {
|
|
541
|
-
'parquet-wasm': '^0.6.0',
|
|
542
|
-
'apache-arrow': '^17.0.0',
|
|
543
|
-
},
|
|
544
|
-
}, null, 2));
|
|
545
|
-
// tsconfig.json
|
|
546
|
-
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
547
|
-
compilerOptions: {
|
|
548
|
-
target: 'ES2022',
|
|
549
|
-
module: 'ESNext',
|
|
550
|
-
moduleResolution: 'bundler',
|
|
551
|
-
strict: true,
|
|
552
|
-
esModuleInterop: true,
|
|
553
|
-
skipLibCheck: true,
|
|
554
|
-
outDir: 'dist',
|
|
555
|
-
declaration: true,
|
|
556
|
-
lib: ['ES2022'],
|
|
557
|
-
},
|
|
558
|
-
include: ['src'],
|
|
559
|
-
exclude: ['node_modules', 'dist'],
|
|
560
|
-
}, null, 2));
|
|
561
|
-
// Create src directory
|
|
562
|
-
mkdirSync(join(targetDir, 'src'));
|
|
563
|
-
// src/example.ts - Basic client example
|
|
564
|
-
writeFileSync(join(targetDir, 'src', 'example.ts'), `/**
|
|
565
|
-
* IFC-Lite Server Client Example
|
|
566
|
-
*
|
|
567
|
-
* This example demonstrates how to use the IFC-Lite server to parse IFC files.
|
|
568
|
-
* The server handles heavy geometry processing, caching, and streaming.
|
|
569
|
-
*
|
|
570
|
-
* Prerequisites:
|
|
571
|
-
* 1. Start the server: docker compose up -d
|
|
572
|
-
* 2. Run this example: npm run example
|
|
573
|
-
*/
|
|
574
|
-
|
|
575
|
-
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
576
|
-
import { readFileSync, existsSync } from 'fs';
|
|
577
|
-
|
|
578
|
-
// Server URL - matches docker-compose.yml port mapping
|
|
579
|
-
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3001';
|
|
580
|
-
|
|
581
|
-
async function main() {
|
|
582
|
-
// Initialize client
|
|
583
|
-
const client = new IfcServerClient({
|
|
584
|
-
baseUrl: SERVER_URL,
|
|
585
|
-
timeout: 300000, // 5 minutes for large files
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
// Check server health
|
|
589
|
-
console.log('Checking server health...');
|
|
590
|
-
try {
|
|
591
|
-
const health = await client.health();
|
|
592
|
-
console.log(\`Server status: \${health.status}\`);
|
|
593
|
-
console.log(\`Server version: \${health.version || 'unknown'}\`);
|
|
594
|
-
} catch (error) {
|
|
595
|
-
console.error('Failed to connect to server. Is it running?');
|
|
596
|
-
console.error('Start it with: docker compose up -d');
|
|
597
|
-
process.exit(1);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Get IFC file path from command line or use default
|
|
601
|
-
const ifcPath = process.argv[2];
|
|
602
|
-
|
|
603
|
-
if (!ifcPath) {
|
|
604
|
-
console.log(\`
|
|
605
|
-
Usage: npm run example <path-to-ifc-file>
|
|
606
|
-
|
|
607
|
-
Example:
|
|
608
|
-
npm run example ./model.ifc
|
|
609
|
-
|
|
610
|
-
The server will:
|
|
611
|
-
1. Check if the file is already cached (instant response)
|
|
612
|
-
2. If not cached, parse and process geometry
|
|
613
|
-
3. Cache the result for future requests
|
|
614
|
-
4. Return geometry data ready for rendering
|
|
615
|
-
\`);
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (!existsSync(ifcPath)) {
|
|
620
|
-
console.error(\`File not found: \${ifcPath}\`);
|
|
621
|
-
process.exit(1);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Read IFC file
|
|
625
|
-
console.log(\`\\nParsing: \${ifcPath}\`);
|
|
626
|
-
const buffer = readFileSync(ifcPath);
|
|
627
|
-
console.log(\`File size: \${(buffer.length / 1024 / 1024).toFixed(2)} MB\`);
|
|
628
|
-
|
|
629
|
-
// Parse with Parquet format (most efficient)
|
|
630
|
-
console.log('\\nSending to server...');
|
|
631
|
-
const startTime = performance.now();
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
// Check if Parquet is available (optional dependency)
|
|
635
|
-
const parquetAvailable = await client.isParquetSupported();
|
|
636
|
-
|
|
637
|
-
if (parquetAvailable) {
|
|
638
|
-
console.log('Using Parquet format (15x smaller than JSON)');
|
|
639
|
-
const result = await client.parseParquet(buffer);
|
|
640
|
-
|
|
641
|
-
const elapsed = performance.now() - startTime;
|
|
642
|
-
console.log(\`\\nParsing complete in \${elapsed.toFixed(0)}ms\`);
|
|
643
|
-
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
644
|
-
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
645
|
-
console.log(\` Payload size: \${(result.parquet_stats.payload_size / 1024).toFixed(1)} KB\`);
|
|
646
|
-
console.log(\` Decode time: \${result.parquet_stats.decode_time_ms}ms\`);
|
|
647
|
-
|
|
648
|
-
if (result.stats) {
|
|
649
|
-
console.log(\`\\nServer stats:\`);
|
|
650
|
-
console.log(\` Parse time: \${result.stats.parse_time_ms}ms\`);
|
|
651
|
-
console.log(\` Geometry time: \${result.stats.geometry_time_ms}ms\`);
|
|
652
|
-
console.log(\` Total triangles: \${result.stats.total_triangles}\`);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Show sample mesh data
|
|
656
|
-
if (result.meshes.length > 0) {
|
|
657
|
-
const mesh = result.meshes[0];
|
|
658
|
-
console.log(\`\\nSample mesh:\`);
|
|
659
|
-
console.log(\` Express ID: \${mesh.express_id}\`);
|
|
660
|
-
console.log(\` Vertices: \${mesh.positions.length / 3}\`);
|
|
661
|
-
console.log(\` Triangles: \${mesh.indices.length / 3}\`);
|
|
662
|
-
console.log(\` Color: rgba(\${mesh.color.join(', ')})\`);
|
|
663
|
-
}
|
|
664
|
-
} else {
|
|
665
|
-
// Fallback to JSON format
|
|
666
|
-
console.log('Parquet not available, using JSON format');
|
|
667
|
-
console.log('Install optional deps for smaller payloads: npm install parquet-wasm apache-arrow');
|
|
668
|
-
|
|
669
|
-
const result = await client.parse(buffer);
|
|
670
|
-
|
|
671
|
-
const elapsed = performance.now() - startTime;
|
|
672
|
-
console.log(\`\\nParsing complete in \${elapsed.toFixed(0)}ms\`);
|
|
673
|
-
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
674
|
-
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
675
|
-
}
|
|
676
|
-
} catch (error) {
|
|
677
|
-
console.error('Parse failed:', error);
|
|
678
|
-
process.exit(1);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
main().catch(console.error);
|
|
683
|
-
`);
|
|
684
|
-
// src/example-stream.ts - Streaming example for large files
|
|
685
|
-
writeFileSync(join(targetDir, 'src', 'example-stream.ts'), `/**
|
|
686
|
-
* IFC-Lite Server Streaming Example
|
|
687
|
-
*
|
|
688
|
-
* This example demonstrates streaming parsing for large IFC files.
|
|
689
|
-
* Geometry batches are received progressively, enabling immediate rendering
|
|
690
|
-
* while the server continues processing.
|
|
691
|
-
*
|
|
692
|
-
* Best for: Files > 50MB where you want progressive rendering
|
|
693
|
-
*
|
|
694
|
-
* Prerequisites:
|
|
695
|
-
* 1. Start the server: docker compose up -d
|
|
696
|
-
* 2. Install optional deps: npm install parquet-wasm apache-arrow
|
|
697
|
-
* 3. Run: npm run example:stream <path-to-ifc>
|
|
698
|
-
*/
|
|
699
|
-
|
|
700
|
-
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
701
|
-
import { readFileSync, existsSync } from 'fs';
|
|
702
|
-
|
|
703
|
-
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3001';
|
|
704
|
-
|
|
705
|
-
async function main() {
|
|
706
|
-
const client = new IfcServerClient({
|
|
707
|
-
baseUrl: SERVER_URL,
|
|
708
|
-
timeout: 600000, // 10 minutes for very large files
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
// Check server
|
|
712
|
-
try {
|
|
713
|
-
await client.health();
|
|
714
|
-
console.log('Server connected');
|
|
715
|
-
} catch {
|
|
716
|
-
console.error('Server not available. Start with: docker compose up -d');
|
|
717
|
-
process.exit(1);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const ifcPath = process.argv[2];
|
|
721
|
-
if (!ifcPath || !existsSync(ifcPath)) {
|
|
722
|
-
console.log('Usage: npm run example:stream <path-to-ifc-file>');
|
|
723
|
-
process.exit(1);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const buffer = readFileSync(ifcPath);
|
|
727
|
-
console.log(\`\\nStreaming: \${ifcPath} (\${(buffer.length / 1024 / 1024).toFixed(1)} MB)\`);
|
|
728
|
-
|
|
729
|
-
// Check Parquet support
|
|
730
|
-
const parquetAvailable = await client.isParquetSupported();
|
|
731
|
-
if (!parquetAvailable) {
|
|
732
|
-
console.error('Streaming requires parquet-wasm and apache-arrow.');
|
|
733
|
-
console.error('Install with: npm install parquet-wasm apache-arrow');
|
|
734
|
-
process.exit(1);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
const startTime = performance.now();
|
|
738
|
-
let totalMeshes = 0;
|
|
739
|
-
let batchCount = 0;
|
|
740
|
-
|
|
741
|
-
try {
|
|
742
|
-
// Stream with batch callback
|
|
743
|
-
const result = await client.parseParquetStream(buffer, (batch) => {
|
|
744
|
-
batchCount++;
|
|
745
|
-
totalMeshes += batch.meshes.length;
|
|
746
|
-
|
|
747
|
-
// In a real app, you would render each batch immediately
|
|
748
|
-
console.log(
|
|
749
|
-
\` Batch #\${batch.batch_number}: +\${batch.meshes.length} meshes \` +
|
|
750
|
-
\`(decode: \${batch.decode_time_ms.toFixed(0)}ms) - Total: \${totalMeshes}\`
|
|
751
|
-
);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
const elapsed = performance.now() - startTime;
|
|
755
|
-
console.log(\`\\nStreaming complete!\`);
|
|
756
|
-
console.log(\` Total time: \${elapsed.toFixed(0)}ms\`);
|
|
757
|
-
console.log(\` Batches received: \${batchCount}\`);
|
|
758
|
-
console.log(\` Total meshes: \${result.total_meshes}\`);
|
|
759
|
-
console.log(\` Cache key: \${result.cache_key.substring(0, 16)}...\`);
|
|
760
|
-
|
|
761
|
-
if (result.stats) {
|
|
762
|
-
console.log(\`\\nServer processing:\`);
|
|
763
|
-
console.log(\` Parse: \${result.stats.parse_time_ms}ms\`);
|
|
764
|
-
console.log(\` Geometry: \${result.stats.geometry_time_ms}ms\`);
|
|
765
|
-
console.log(\` Triangles: \${result.stats.total_triangles}\`);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
// Optionally fetch full data model for properties panel
|
|
769
|
-
console.log(\`\\nFetching data model for properties...\`);
|
|
770
|
-
const dataModel = await client.fetchDataModel(result.cache_key);
|
|
771
|
-
if (dataModel) {
|
|
772
|
-
console.log(\` Data model size: \${(dataModel.byteLength / 1024).toFixed(1)} KB\`);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
} catch (error) {
|
|
776
|
-
console.error('Streaming failed:', error);
|
|
777
|
-
process.exit(1);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
main().catch(console.error);
|
|
782
|
-
`);
|
|
783
|
-
// src/index.ts - Library re-export for custom integrations
|
|
784
|
-
writeFileSync(join(targetDir, 'src', 'index.ts'), `/**
|
|
785
|
-
* ${projectName} - IFC Processing Server Client
|
|
786
|
-
*
|
|
787
|
-
* Re-exports the IFC-Lite server client for custom integrations.
|
|
788
|
-
* See example.ts and example-stream.ts for usage examples.
|
|
789
|
-
*/
|
|
790
|
-
|
|
791
|
-
export { IfcServerClient } from '@ifc-lite/server-client';
|
|
792
|
-
export type {
|
|
793
|
-
ServerConfig,
|
|
794
|
-
ParseResponse,
|
|
795
|
-
ParquetParseResponse,
|
|
796
|
-
StreamEvent,
|
|
797
|
-
HealthResponse,
|
|
798
|
-
MetadataResponse,
|
|
799
|
-
} from '@ifc-lite/server-client';
|
|
800
|
-
|
|
801
|
-
// Default server URL
|
|
802
|
-
export const DEFAULT_SERVER_URL = 'http://localhost:3001';
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* Create a pre-configured client for the local Docker server.
|
|
806
|
-
*/
|
|
807
|
-
export function createLocalClient(options?: { timeout?: number }) {
|
|
808
|
-
const { IfcServerClient } = require('@ifc-lite/server-client');
|
|
809
|
-
return new IfcServerClient({
|
|
810
|
-
baseUrl: process.env.SERVER_URL || DEFAULT_SERVER_URL,
|
|
811
|
-
timeout: options?.timeout ?? 300000,
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
`);
|
|
815
|
-
// README.md
|
|
816
|
-
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
817
|
-
|
|
818
|
-
IFC processing server using [IFC-Lite](https://github.com/louistrue/ifc-lite).
|
|
819
|
-
|
|
820
|
-
## Quick Start
|
|
821
|
-
|
|
822
|
-
\`\`\`bash
|
|
823
|
-
# 1. Start the server
|
|
824
|
-
docker compose up -d
|
|
825
|
-
|
|
826
|
-
# 2. Install client dependencies
|
|
827
|
-
npm install
|
|
828
|
-
|
|
829
|
-
# 3. Run the example (replace with your IFC file)
|
|
830
|
-
npm run example ./your-model.ifc
|
|
831
|
-
\`\`\`
|
|
832
|
-
|
|
833
|
-
## Features
|
|
834
|
-
|
|
835
|
-
| Feature | Description |
|
|
836
|
-
|---------|-------------|
|
|
837
|
-
| **Content-Addressable Cache** | Same file = instant response (no reprocessing) |
|
|
838
|
-
| **Parquet Format** | 15x smaller payloads than JSON |
|
|
839
|
-
| **Streaming** | Progressive geometry for large files |
|
|
840
|
-
| **Parallel Processing** | Multi-threaded geometry processing |
|
|
841
|
-
|
|
842
|
-
## Architecture
|
|
843
|
-
|
|
844
|
-
\`\`\`
|
|
845
|
-
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
846
|
-
│ Your App │────▶│ IFC-Lite Server │────▶│ Cache │
|
|
847
|
-
│ (TypeScript) │◀────│ (Docker) │◀────│ (Volume) │
|
|
848
|
-
└─────────────────┘ └──────────────────┘ └─────────────┘
|
|
849
|
-
\`\`\`
|
|
850
|
-
|
|
851
|
-
## Scripts
|
|
852
|
-
|
|
853
|
-
| Command | Description |
|
|
854
|
-
|---------|-------------|
|
|
855
|
-
| \`npm run example\` | Basic parsing example |
|
|
856
|
-
| \`npm run example:stream\` | Streaming example for large files |
|
|
857
|
-
| \`npm run server:start\` | Start server in background |
|
|
858
|
-
| \`npm run server:stop\` | Stop server |
|
|
859
|
-
| \`npm run server:logs\` | View server logs |
|
|
860
|
-
| \`npm run server:dev\` | Start with debug logging |
|
|
861
|
-
|
|
862
|
-
## Configuration
|
|
863
|
-
|
|
864
|
-
Copy \`.env.example\` to \`.env\` to customize:
|
|
865
|
-
|
|
866
|
-
| Variable | Default | Description |
|
|
867
|
-
|----------|---------|-------------|
|
|
868
|
-
| \`PORT\` | 3001 | Server port |
|
|
869
|
-
| \`MAX_FILE_SIZE_MB\` | 500 | Max upload size |
|
|
870
|
-
| \`WORKER_THREADS\` | 4 | Parallel processing threads |
|
|
871
|
-
| \`CACHE_MAX_AGE_DAYS\` | 7 | Cache retention |
|
|
872
|
-
|
|
873
|
-
See \`.env.example\` for all options with documentation.
|
|
874
|
-
|
|
875
|
-
## API Endpoints
|
|
876
|
-
|
|
877
|
-
| Endpoint | Description |
|
|
878
|
-
|----------|-------------|
|
|
879
|
-
| \`GET /api/v1/health\` | Health check |
|
|
880
|
-
| \`POST /api/v1/parse\` | Full parse (JSON response) |
|
|
881
|
-
| \`POST /api/v1/parse/parquet\` | Full parse (Parquet, 15x smaller) |
|
|
882
|
-
| \`POST /api/v1/parse/parquet-stream\` | Streaming parse (SSE + Parquet) |
|
|
883
|
-
| \`GET /api/v1/cache/check/:hash\` | Check if file is cached |
|
|
884
|
-
|
|
885
|
-
## Client Usage
|
|
886
|
-
|
|
887
|
-
### Basic Parse
|
|
888
|
-
|
|
889
|
-
\`\`\`typescript
|
|
890
|
-
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
891
|
-
|
|
892
|
-
const client = new IfcServerClient({
|
|
893
|
-
baseUrl: 'http://localhost:3001'
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
const result = await client.parseParquet(ifcBuffer);
|
|
897
|
-
console.log(\`Meshes: \${result.meshes.length}\`);
|
|
898
|
-
\`\`\`
|
|
899
|
-
|
|
900
|
-
### Streaming (Large Files)
|
|
901
|
-
|
|
902
|
-
\`\`\`typescript
|
|
903
|
-
await client.parseParquetStream(ifcBuffer, (batch) => {
|
|
904
|
-
// Render each batch immediately
|
|
905
|
-
for (const mesh of batch.meshes) {
|
|
906
|
-
scene.addMesh(mesh);
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
\`\`\`
|
|
910
|
-
|
|
911
|
-
### Cache-First Pattern
|
|
912
|
-
|
|
913
|
-
\`\`\`typescript
|
|
914
|
-
// Client automatically:
|
|
915
|
-
// 1. Computes file hash locally
|
|
916
|
-
// 2. Checks server cache
|
|
917
|
-
// 3. Skips upload if cached (instant response!)
|
|
918
|
-
const result = await client.parseParquet(ifcBuffer);
|
|
919
|
-
\`\`\`
|
|
920
|
-
|
|
921
|
-
## Production Deployment
|
|
922
|
-
|
|
923
|
-
### Railway / Render / Fly.io
|
|
924
|
-
|
|
925
|
-
The Docker image works on any container platform:
|
|
926
|
-
|
|
927
|
-
\`\`\`bash
|
|
928
|
-
# Pull and run
|
|
929
|
-
docker pull ghcr.io/louistrue/ifc-lite-server:latest
|
|
930
|
-
docker run -p 8080:8080 -v ifc-cache:/app/cache ghcr.io/louistrue/ifc-lite-server
|
|
931
|
-
\`\`\`
|
|
932
|
-
|
|
933
|
-
### Environment Variables
|
|
934
|
-
|
|
935
|
-
Set these in your deployment platform:
|
|
936
|
-
|
|
937
|
-
\`\`\`
|
|
938
|
-
PORT=8080
|
|
939
|
-
MAX_FILE_SIZE_MB=500
|
|
940
|
-
WORKER_THREADS=4
|
|
941
|
-
CACHE_MAX_AGE_DAYS=30
|
|
942
|
-
RUST_LOG=info
|
|
943
|
-
\`\`\`
|
|
944
|
-
|
|
945
|
-
## Learn More
|
|
946
|
-
|
|
947
|
-
- [IFC-Lite Documentation](https://louistrue.github.io/ifc-lite/)
|
|
948
|
-
- [Server API Reference](https://louistrue.github.io/ifc-lite/api/server/)
|
|
949
|
-
- [GitHub Repository](https://github.com/louistrue/ifc-lite)
|
|
950
|
-
`);
|
|
951
|
-
console.log(' Created docker-compose.yml');
|
|
952
|
-
console.log(' Created docker-compose.dev.yml');
|
|
953
|
-
console.log(' Created .env.example');
|
|
954
|
-
console.log(' Created .env');
|
|
955
|
-
console.log(' Created package.json');
|
|
956
|
-
console.log(' Created tsconfig.json');
|
|
957
|
-
console.log(' Created src/example.ts');
|
|
958
|
-
console.log(' Created src/example-stream.ts');
|
|
959
|
-
console.log(' Created src/index.ts');
|
|
960
|
-
console.log(' Created README.md');
|
|
961
|
-
console.log(' Created .gitignore');
|
|
962
|
-
console.log(' Created .dockerignore');
|
|
963
|
-
}
|
|
964
|
-
function createServerNativeTemplate(targetDir, projectName) {
|
|
965
|
-
const latestVersion = getLatestVersion();
|
|
966
|
-
// package.json
|
|
967
|
-
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
968
|
-
name: projectName,
|
|
969
|
-
version: '0.1.0',
|
|
970
|
-
type: 'module',
|
|
971
|
-
description: 'IFC processing server (native binary) with TypeScript client',
|
|
972
|
-
scripts: {
|
|
973
|
-
'server:start': 'npx @ifc-lite/server-bin',
|
|
974
|
-
'server:download': 'npx @ifc-lite/server-bin download',
|
|
975
|
-
'server:info': 'npx @ifc-lite/server-bin info',
|
|
976
|
-
'example': 'npx tsx src/example.ts',
|
|
977
|
-
'example:stream': 'npx tsx src/example-stream.ts',
|
|
978
|
-
'build': 'tsc',
|
|
979
|
-
'typecheck': 'tsc --noEmit',
|
|
980
|
-
},
|
|
981
|
-
dependencies: {
|
|
982
|
-
'@ifc-lite/server-bin': latestVersion,
|
|
983
|
-
'@ifc-lite/server-client': latestVersion,
|
|
984
|
-
},
|
|
985
|
-
devDependencies: {
|
|
986
|
-
'typescript': '^5.3.0',
|
|
987
|
-
'tsx': '^4.0.0',
|
|
988
|
-
'@types/node': '^20.0.0',
|
|
989
|
-
},
|
|
990
|
-
optionalDependencies: {
|
|
991
|
-
'parquet-wasm': '^0.6.0',
|
|
992
|
-
'apache-arrow': '^17.0.0',
|
|
993
|
-
},
|
|
994
|
-
}, null, 2));
|
|
995
|
-
// tsconfig.json
|
|
996
|
-
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
997
|
-
compilerOptions: {
|
|
998
|
-
target: 'ES2022',
|
|
999
|
-
module: 'ESNext',
|
|
1000
|
-
moduleResolution: 'bundler',
|
|
1001
|
-
strict: true,
|
|
1002
|
-
esModuleInterop: true,
|
|
1003
|
-
skipLibCheck: true,
|
|
1004
|
-
outDir: 'dist',
|
|
1005
|
-
declaration: true,
|
|
1006
|
-
lib: ['ES2022'],
|
|
1007
|
-
},
|
|
1008
|
-
include: ['src'],
|
|
1009
|
-
exclude: ['node_modules', 'dist'],
|
|
1010
|
-
}, null, 2));
|
|
1011
|
-
// .env.example
|
|
1012
|
-
writeFileSync(join(targetDir, '.env.example'), `# IFC-Lite Server Configuration (Native Binary)
|
|
1013
|
-
# These environment variables configure the server
|
|
1014
|
-
|
|
1015
|
-
# =============================================================================
|
|
1016
|
-
# SERVER SETTINGS
|
|
1017
|
-
# =============================================================================
|
|
1018
|
-
|
|
1019
|
-
# Server port
|
|
1020
|
-
PORT=8080
|
|
1021
|
-
|
|
1022
|
-
# Log level: error, warn, info, debug, trace
|
|
1023
|
-
RUST_LOG=info
|
|
1024
|
-
|
|
1025
|
-
# =============================================================================
|
|
1026
|
-
# FILE PROCESSING
|
|
1027
|
-
# =============================================================================
|
|
1028
|
-
|
|
1029
|
-
# Maximum IFC file size in megabytes
|
|
1030
|
-
MAX_FILE_SIZE_MB=500
|
|
1031
|
-
|
|
1032
|
-
# Request timeout in seconds
|
|
1033
|
-
REQUEST_TIMEOUT_SECS=300
|
|
1034
|
-
|
|
1035
|
-
# Number of worker threads for parallel processing
|
|
1036
|
-
# Default: number of CPU cores
|
|
1037
|
-
WORKER_THREADS=4
|
|
1038
|
-
|
|
1039
|
-
# =============================================================================
|
|
1040
|
-
# STREAMING
|
|
1041
|
-
# =============================================================================
|
|
1042
|
-
|
|
1043
|
-
# Initial batch size for fast first frame
|
|
1044
|
-
INITIAL_BATCH_SIZE=100
|
|
1045
|
-
|
|
1046
|
-
# Maximum batch size for throughput
|
|
1047
|
-
MAX_BATCH_SIZE=1000
|
|
1048
|
-
|
|
1049
|
-
# =============================================================================
|
|
1050
|
-
# CACHING
|
|
1051
|
-
# =============================================================================
|
|
1052
|
-
|
|
1053
|
-
# Cache directory (relative or absolute path)
|
|
1054
|
-
CACHE_DIR=./.cache
|
|
1055
|
-
|
|
1056
|
-
# Cache retention in days
|
|
1057
|
-
CACHE_MAX_AGE_DAYS=7
|
|
1058
|
-
`);
|
|
1059
|
-
// .gitignore
|
|
1060
|
-
writeFileSync(join(targetDir, '.gitignore'), `# Dependencies
|
|
1061
|
-
node_modules/
|
|
1062
|
-
|
|
1063
|
-
# Build output
|
|
1064
|
-
dist/
|
|
1065
|
-
|
|
1066
|
-
# Environment files
|
|
1067
|
-
.env
|
|
1068
|
-
.env.local
|
|
1069
|
-
.env.*.local
|
|
1070
|
-
|
|
1071
|
-
# Cache directory
|
|
1072
|
-
.cache/
|
|
1073
|
-
|
|
1074
|
-
# IDE
|
|
1075
|
-
.idea/
|
|
1076
|
-
.vscode/
|
|
1077
|
-
*.swp
|
|
1078
|
-
*.swo
|
|
1079
|
-
|
|
1080
|
-
# OS
|
|
1081
|
-
.DS_Store
|
|
1082
|
-
Thumbs.db
|
|
1083
|
-
|
|
1084
|
-
# Logs
|
|
1085
|
-
*.log
|
|
1086
|
-
npm-debug.log*
|
|
1087
|
-
`);
|
|
1088
|
-
// Create src directory
|
|
1089
|
-
mkdirSync(join(targetDir, 'src'));
|
|
1090
|
-
// src/example.ts
|
|
1091
|
-
writeFileSync(join(targetDir, 'src', 'example.ts'), `/**
|
|
1092
|
-
* IFC-Lite Native Server Example
|
|
1093
|
-
*
|
|
1094
|
-
* This example demonstrates using the IFC-Lite server with native binary.
|
|
1095
|
-
* No Docker required - the binary is downloaded and run automatically.
|
|
1096
|
-
*
|
|
1097
|
-
* Usage:
|
|
1098
|
-
* 1. Start the server: npm run server:start
|
|
1099
|
-
* 2. In another terminal: npm run example ./your-model.ifc
|
|
1100
|
-
*/
|
|
1101
|
-
|
|
1102
|
-
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
1103
|
-
import { readFileSync, existsSync } from 'fs';
|
|
1104
|
-
|
|
1105
|
-
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:8080';
|
|
1106
|
-
|
|
1107
|
-
async function main() {
|
|
1108
|
-
const client = new IfcServerClient({
|
|
1109
|
-
baseUrl: SERVER_URL,
|
|
1110
|
-
timeout: 300000,
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
// Check server
|
|
1114
|
-
console.log('Checking server health...');
|
|
1115
|
-
try {
|
|
1116
|
-
const health = await client.health();
|
|
1117
|
-
console.log(\`Server status: \${health.status}\`);
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
console.error('Failed to connect to server.');
|
|
1120
|
-
console.error('Start it with: npm run server:start');
|
|
1121
|
-
process.exit(1);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
const ifcPath = process.argv[2];
|
|
1125
|
-
if (!ifcPath) {
|
|
1126
|
-
console.log(\`
|
|
1127
|
-
Usage: npm run example <path-to-ifc-file>
|
|
1128
|
-
|
|
1129
|
-
Example:
|
|
1130
|
-
npm run example ./model.ifc
|
|
1131
|
-
\`);
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
if (!existsSync(ifcPath)) {
|
|
1136
|
-
console.error(\`File not found: \${ifcPath}\`);
|
|
1137
|
-
process.exit(1);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const buffer = readFileSync(ifcPath);
|
|
1141
|
-
console.log(\`\\nParsing: \${ifcPath}\`);
|
|
1142
|
-
console.log(\`File size: \${(buffer.length / 1024 / 1024).toFixed(2)} MB\`);
|
|
1143
|
-
|
|
1144
|
-
const startTime = performance.now();
|
|
1145
|
-
|
|
1146
|
-
try {
|
|
1147
|
-
const parquetAvailable = await client.isParquetSupported();
|
|
1148
|
-
|
|
1149
|
-
if (parquetAvailable) {
|
|
1150
|
-
console.log('Using Parquet format (15x smaller)');
|
|
1151
|
-
const result = await client.parseParquet(buffer);
|
|
1152
|
-
const elapsed = performance.now() - startTime;
|
|
1153
|
-
|
|
1154
|
-
console.log(\`\\nComplete in \${elapsed.toFixed(0)}ms\`);
|
|
1155
|
-
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
1156
|
-
console.log(\` Payload: \${(result.parquet_stats.payload_size / 1024).toFixed(1)} KB\`);
|
|
1157
|
-
|
|
1158
|
-
if (result.stats) {
|
|
1159
|
-
console.log(\` Triangles: \${result.stats.total_triangles}\`);
|
|
1160
|
-
}
|
|
1161
|
-
} else {
|
|
1162
|
-
console.log('Using JSON format');
|
|
1163
|
-
const result = await client.parse(buffer);
|
|
1164
|
-
const elapsed = performance.now() - startTime;
|
|
1165
|
-
|
|
1166
|
-
console.log(\`\\nComplete in \${elapsed.toFixed(0)}ms\`);
|
|
1167
|
-
console.log(\` Meshes: \${result.meshes.length}\`);
|
|
1168
|
-
}
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
console.error('Parse failed:', error);
|
|
1171
|
-
process.exit(1);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
main().catch(console.error);
|
|
1176
|
-
`);
|
|
1177
|
-
// src/example-stream.ts
|
|
1178
|
-
writeFileSync(join(targetDir, 'src', 'example-stream.ts'), `/**
|
|
1179
|
-
* IFC-Lite Native Server Streaming Example
|
|
1180
|
-
*
|
|
1181
|
-
* For large files (>50MB) - geometry arrives in batches.
|
|
1182
|
-
*
|
|
1183
|
-
* Usage:
|
|
1184
|
-
* 1. Start the server: npm run server:start
|
|
1185
|
-
* 2. In another terminal: npm run example:stream ./large-model.ifc
|
|
1186
|
-
*/
|
|
1187
|
-
|
|
1188
|
-
import { IfcServerClient } from '@ifc-lite/server-client';
|
|
1189
|
-
import { readFileSync, existsSync } from 'fs';
|
|
1190
|
-
|
|
1191
|
-
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:8080';
|
|
1192
|
-
|
|
1193
|
-
async function main() {
|
|
1194
|
-
const client = new IfcServerClient({
|
|
1195
|
-
baseUrl: SERVER_URL,
|
|
1196
|
-
timeout: 600000,
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
|
-
try {
|
|
1200
|
-
await client.health();
|
|
1201
|
-
console.log('Server connected');
|
|
1202
|
-
} catch {
|
|
1203
|
-
console.error('Server not available. Start with: npm run server:start');
|
|
1204
|
-
process.exit(1);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
const ifcPath = process.argv[2];
|
|
1208
|
-
if (!ifcPath || !existsSync(ifcPath)) {
|
|
1209
|
-
console.log('Usage: npm run example:stream <path-to-ifc-file>');
|
|
1210
|
-
process.exit(1);
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
const parquetAvailable = await client.isParquetSupported();
|
|
1214
|
-
if (!parquetAvailable) {
|
|
1215
|
-
console.error('Streaming requires parquet-wasm and apache-arrow.');
|
|
1216
|
-
console.error('Install with: npm install parquet-wasm apache-arrow');
|
|
1217
|
-
process.exit(1);
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
const buffer = readFileSync(ifcPath);
|
|
1221
|
-
console.log(\`\\nStreaming: \${ifcPath} (\${(buffer.length / 1024 / 1024).toFixed(1)} MB)\`);
|
|
1222
|
-
|
|
1223
|
-
const startTime = performance.now();
|
|
1224
|
-
let totalMeshes = 0;
|
|
1225
|
-
|
|
1226
|
-
try {
|
|
1227
|
-
const result = await client.parseParquetStream(buffer, (batch) => {
|
|
1228
|
-
totalMeshes += batch.meshes.length;
|
|
1229
|
-
console.log(\` Batch #\${batch.batch_number}: +\${batch.meshes.length} meshes (total: \${totalMeshes})\`);
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
const elapsed = performance.now() - startTime;
|
|
1233
|
-
console.log(\`\\nComplete in \${elapsed.toFixed(0)}ms\`);
|
|
1234
|
-
console.log(\` Total meshes: \${result.total_meshes}\`);
|
|
1235
|
-
|
|
1236
|
-
} catch (error) {
|
|
1237
|
-
console.error('Streaming failed:', error);
|
|
1238
|
-
process.exit(1);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
main().catch(console.error);
|
|
1243
|
-
`);
|
|
1244
|
-
// src/index.ts
|
|
1245
|
-
writeFileSync(join(targetDir, 'src', 'index.ts'), `/**
|
|
1246
|
-
* ${projectName} - IFC Processing Server (Native Binary)
|
|
1247
|
-
*
|
|
1248
|
-
* Re-exports for custom integrations.
|
|
1249
|
-
*/
|
|
1250
|
-
|
|
1251
|
-
export { IfcServerClient } from '@ifc-lite/server-client';
|
|
1252
|
-
export type {
|
|
1253
|
-
ServerConfig,
|
|
1254
|
-
ParseResponse,
|
|
1255
|
-
ParquetParseResponse,
|
|
1256
|
-
StreamEvent,
|
|
1257
|
-
HealthResponse,
|
|
1258
|
-
MetadataResponse,
|
|
1259
|
-
} from '@ifc-lite/server-client';
|
|
1260
|
-
|
|
1261
|
-
export const DEFAULT_SERVER_URL = 'http://localhost:8080';
|
|
1262
|
-
`);
|
|
1263
|
-
// README.md
|
|
1264
|
-
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
1265
|
-
|
|
1266
|
-
IFC processing server using native binary - no Docker required.
|
|
1267
|
-
|
|
1268
|
-
## Quick Start
|
|
1269
|
-
|
|
1270
|
-
\`\`\`bash
|
|
1271
|
-
# Install dependencies (downloads server binary automatically)
|
|
1272
|
-
npm install
|
|
1273
|
-
|
|
1274
|
-
# Start the server
|
|
1275
|
-
npm run server:start
|
|
1276
|
-
|
|
1277
|
-
# In another terminal, run the example
|
|
1278
|
-
npm run example ./your-model.ifc
|
|
1279
|
-
\`\`\`
|
|
1280
|
-
|
|
1281
|
-
## Features
|
|
1282
|
-
|
|
1283
|
-
| Feature | Description |
|
|
1284
|
-
|---------|-------------|
|
|
1285
|
-
| **No Docker Required** | Native binary runs directly |
|
|
1286
|
-
| **Auto-Download** | Binary downloaded on first run |
|
|
1287
|
-
| **Cross-Platform** | macOS, Linux, Windows support |
|
|
1288
|
-
| **Content-Addressable Cache** | Same file = instant response |
|
|
1289
|
-
|
|
1290
|
-
## Scripts
|
|
1291
|
-
|
|
1292
|
-
| Command | Description |
|
|
1293
|
-
|---------|-------------|
|
|
1294
|
-
| \`npm run server:start\` | Start the IFC-Lite server |
|
|
1295
|
-
| \`npm run server:download\` | Download binary without starting |
|
|
1296
|
-
| \`npm run server:info\` | Show platform and binary info |
|
|
1297
|
-
| \`npm run example\` | Basic parsing example |
|
|
1298
|
-
| \`npm run example:stream\` | Streaming example for large files |
|
|
1299
|
-
|
|
1300
|
-
## Configuration
|
|
1301
|
-
|
|
1302
|
-
Set environment variables to configure the server:
|
|
1303
|
-
|
|
1304
|
-
\`\`\`bash
|
|
1305
|
-
# Custom port
|
|
1306
|
-
PORT=3001 npm run server:start
|
|
1307
|
-
|
|
1308
|
-
# Debug logging
|
|
1309
|
-
RUST_LOG=debug npm run server:start
|
|
1310
|
-
|
|
1311
|
-
# Multiple options
|
|
1312
|
-
PORT=3001 WORKER_THREADS=8 npm run server:start
|
|
1313
|
-
\`\`\`
|
|
1314
|
-
|
|
1315
|
-
See \`.env.example\` for all options.
|
|
1316
|
-
|
|
1317
|
-
## API Endpoints
|
|
1318
|
-
|
|
1319
|
-
| Endpoint | Description |
|
|
1320
|
-
|----------|-------------|
|
|
1321
|
-
| \`GET /api/v1/health\` | Health check |
|
|
1322
|
-
| \`POST /api/v1/parse\` | Full parse (JSON) |
|
|
1323
|
-
| \`POST /api/v1/parse/parquet\` | Full parse (Parquet, 15x smaller) |
|
|
1324
|
-
| \`POST /api/v1/parse/parquet-stream\` | Streaming parse |
|
|
1325
|
-
| \`GET /api/v1/cache/check/:hash\` | Check cache |
|
|
1326
|
-
|
|
1327
|
-
## Supported Platforms
|
|
1328
|
-
|
|
1329
|
-
| Platform | Architecture | Status |
|
|
1330
|
-
|----------|--------------|--------|
|
|
1331
|
-
| macOS | Intel (x64) | ✅ |
|
|
1332
|
-
| macOS | Apple Silicon (arm64) | ✅ |
|
|
1333
|
-
| Linux | x64 | ✅ |
|
|
1334
|
-
| Linux | arm64 | ✅ |
|
|
1335
|
-
| Windows | x64 | ✅ |
|
|
1336
|
-
|
|
1337
|
-
## Alternatives
|
|
1338
|
-
|
|
1339
|
-
If native binaries don't work for your platform:
|
|
1340
|
-
|
|
1341
|
-
\`\`\`bash
|
|
1342
|
-
# Use Docker instead
|
|
1343
|
-
npx create-ifc-lite my-app --template server
|
|
1344
|
-
\`\`\`
|
|
1345
|
-
|
|
1346
|
-
## Learn More
|
|
1347
|
-
|
|
1348
|
-
- [IFC-Lite Documentation](https://louistrue.github.io/ifc-lite/)
|
|
1349
|
-
- [GitHub Repository](https://github.com/louistrue/ifc-lite)
|
|
1350
|
-
`);
|
|
1351
|
-
console.log(' Created package.json');
|
|
1352
|
-
console.log(' Created tsconfig.json');
|
|
1353
|
-
console.log(' Created .env.example');
|
|
1354
|
-
console.log(' Created .gitignore');
|
|
1355
|
-
console.log(' Created src/example.ts');
|
|
1356
|
-
console.log(' Created src/example-stream.ts');
|
|
1357
|
-
console.log(' Created src/index.ts');
|
|
1358
|
-
console.log(' Created README.md');
|
|
1359
|
-
}
|
|
1360
120
|
main().catch(console.error);
|