@fractalq/client 2.1.1

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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/examples/async-callback.js +88 -0
  4. package/examples/basic-usage.js +29 -0
  5. package/examples/image-upscaling.js +48 -0
  6. package/examples/pipeline-processing.js +51 -0
  7. package/examples/react-component.jsx +97 -0
  8. package/examples/smoke-config.mjs +26 -0
  9. package/index.d.ts +81 -0
  10. package/index.js +45 -0
  11. package/package.json +65 -0
  12. package/src/client/FractalQClient.js +132 -0
  13. package/src/client/authorization.js +11 -0
  14. package/src/client/build-direct-body.js +10 -0
  15. package/src/client/build-proxy-body.js +26 -0
  16. package/src/client/effective-job-id.js +9 -0
  17. package/src/client/execute-response.js +18 -0
  18. package/src/client/execute-url.js +9 -0
  19. package/src/client/http-error.js +12 -0
  20. package/src/client/resolve-base-url.js +39 -0
  21. package/src/client/temp-inputs.js +20 -0
  22. package/src/config/api-key.js +13 -0
  23. package/src/config/base-url.js +17 -0
  24. package/src/config/index.js +21 -0
  25. package/src/config/node-url.js +8 -0
  26. package/src/config/session-lookup.js +26 -0
  27. package/src/image/ImageProcessor.js +86 -0
  28. package/src/image/extract-upscaled-output.js +5 -0
  29. package/src/image/file-to-base64.js +15 -0
  30. package/src/image/index.js +2 -0
  31. package/src/image/prepare-image-input.js +11 -0
  32. package/src/image/resolve-callback-url.js +10 -0
  33. package/src/pipeline/PipelineProcessor.js +32 -0
  34. package/src/pipeline/apply-stage-result.js +9 -0
  35. package/src/pipeline/index.js +2 -0
  36. package/src/pipeline/merge-node-inputs.js +9 -0
  37. package/src/storage/firebase/image-to-blob.js +21 -0
  38. package/src/storage/firebase/index.js +2 -0
  39. package/src/storage/firebase/instance.js +52 -0
  40. package/src/storage/firebase/upload-task.js +31 -0
  41. package/src/storage/firebase/upload-to-firebase.js +33 -0
  42. package/src/storage/shared/base64-data-url-to-blob.js +24 -0
  43. package/src/storage/temp/TempFileManager.js +102 -0
  44. package/src/storage/temp/generate-filename.js +22 -0
  45. package/src/storage/temp/index.js +2 -0
  46. package/src/storage/temp/input-to-blob.js +18 -0
  47. package/src/storage/temp/mime-extension-map.js +12 -0
  48. package/src/storage/temp/upload-core.js +49 -0
  49. package/useFractalQ.d.ts +29 -0
  50. package/useFractalQ.js +126 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) FractalQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @fractalq/client
2
+
3
+ JavaScript client for [FractalQ](https://fractalq.com). **API keys are never read from environment variables inside this package** — pass them from your app (DB, vault, or your own `process.env` in application code).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @fractalq/client
9
+ ```
10
+
11
+ ## Config (server)
12
+
13
+ ```js
14
+ import { getSignalQApiUrl, getFractalQApiKey } from '@fractalq/client';
15
+
16
+ const key = process.env.MY_APP_FRACTALQ_NODE_KEY; // your app loads this, not the library
17
+ const url = getSignalQApiUrl(getFractalQApiKey(key));
18
+ ```
19
+
20
+ ## Browser (proxy + Firebase token)
21
+
22
+ ```js
23
+ import FractalQ from '@fractalq/client';
24
+
25
+ const client = new FractalQ({
26
+ useBackendProxy: true,
27
+ sessionApiKey: nodeKeyFromYourUi,
28
+ authTokenProvider: async () => {
29
+ const user = getFirebaseAuth().currentUser;
30
+ return user ? user.getIdToken() : null;
31
+ },
32
+ });
33
+
34
+ await client.execute(null, { input_0: imageUrl }, { async: true, callbackUrl, memeId });
35
+ ```
36
+
37
+ ## Publish
38
+
39
+ ```bash
40
+ cd fractalq-client
41
+ npm login
42
+ npm publish --access public
43
+ ```
44
+
45
+ Scoped package `@fractalq/client` requires npm org access or publish under your scope.
46
+
47
+ ## Local link (monorepo)
48
+
49
+ In WOKENMEME `package.json`:
50
+
51
+ ```json
52
+ "@fractalq/client": "file:../fractalq-client"
53
+ ```
54
+
55
+ Then `yarn` or `npm install`.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Async Processing with Callback Example
3
+ *
4
+ * Example showing how to use async processing with webhook callbacks
5
+ * for long-running operations
6
+ */
7
+
8
+ import FractalQ from '@fractalq/client';
9
+ import express from 'express';
10
+
11
+ const fractalq = new FractalQ({
12
+ apiKey: process.env.FRACTALQ_API_KEY
13
+ });
14
+
15
+ // Setup Express server to receive callbacks
16
+ const app = express();
17
+ app.use(express.json());
18
+
19
+ // Webhook endpoint to receive processing results
20
+ app.post('/webhook/fractalq-callback', (req, res) => {
21
+ const { success, data, jobId, error } = req.body;
22
+
23
+ console.log('Received callback for job:', jobId);
24
+
25
+ if (success) {
26
+ console.log('Processing complete!');
27
+ console.log('Result:', data.output_0);
28
+
29
+ // Handle the result (save to database, notify user, etc.)
30
+ handleProcessingComplete(jobId, data);
31
+ } else {
32
+ console.error('Processing failed:', error);
33
+ handleProcessingError(jobId, error);
34
+ }
35
+
36
+ res.status(200).json({ received: true });
37
+ });
38
+
39
+ // Start async processing
40
+ async function startAsyncProcessing(imageFile, userId) {
41
+ try {
42
+ // Start processing with callback URL
43
+ const result = await fractalq.execute('heavy_processing_node', {
44
+ input_0: imageFile,
45
+ user_id: userId
46
+ }, {
47
+ callbackUrl: 'https://yourapp.com/webhook/fractalq-callback'
48
+ });
49
+
50
+ console.log('Processing started');
51
+ console.log('Job ID:', result.jobId);
52
+
53
+ // Store job ID in database for tracking
54
+ await saveJobToDatabase(userId, result.jobId);
55
+
56
+ return result.jobId;
57
+ } catch (error) {
58
+ console.error('Failed to start processing:', error);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ function handleProcessingComplete(jobId, data) {
64
+ // Implement your logic here
65
+ console.log(`Job ${jobId} completed successfully`);
66
+ // Update database, send notification, etc.
67
+ }
68
+
69
+ function handleProcessingError(jobId, error) {
70
+ // Implement your error handling here
71
+ console.log(`Job ${jobId} failed:`, error);
72
+ // Update database, notify user of failure, etc.
73
+ }
74
+
75
+ async function saveJobToDatabase(userId, jobId) {
76
+ // Implement your database logic
77
+ console.log(`Saving job ${jobId} for user ${userId}`);
78
+ }
79
+
80
+ // Start server
81
+ const PORT = process.env.PORT || 3000;
82
+ app.listen(PORT, () => {
83
+ console.log(`Webhook server listening on port ${PORT}`);
84
+ });
85
+
86
+ // Example usage
87
+ // startAsyncProcessing(imageFile, 'user123');
88
+
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Basic Usage Example
3
+ *
4
+ * Simple example showing how to initialize and use the FractalQ client
5
+ */
6
+
7
+ import FractalQ from '@fractalq/client';
8
+
9
+ // Load the key in YOUR app (env / DB / vault). @fractalq/client does not read env for secrets.
10
+ const nodeApiKey = process.env.FRACTALQ_API_KEY;
11
+ if (!nodeApiKey) throw new Error('Set FRACTALQ_API_KEY on the host; never commit keys to git or npm.');
12
+ const fractalq = new FractalQ({ apiKey: nodeApiKey });
13
+
14
+ async function basicExample() {
15
+ try {
16
+ // Execute a node
17
+ const result = await fractalq.execute('your_node_id', {
18
+ input_0: 'https://example.com/image.jpg'
19
+ });
20
+
21
+ console.log('Success!', result.data);
22
+ console.log('Output:', result.data.output_0);
23
+ } catch (error) {
24
+ console.error('Error:', error.message);
25
+ }
26
+ }
27
+
28
+ basicExample();
29
+
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Image Upscaling Example
3
+ *
4
+ * Example showing how to upscale images with Firebase upload
5
+ */
6
+
7
+ import FractalQ from '@fractalq/client';
8
+ import { initializeApp } from 'firebase/app';
9
+ import { getStorage } from 'firebase/storage';
10
+
11
+ // Initialize Firebase
12
+ const app = initializeApp({
13
+ apiKey: process.env.FIREBASE_API_KEY,
14
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN,
15
+ projectId: process.env.FIREBASE_PROJECT_ID,
16
+ storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
17
+ });
18
+
19
+ const storage = getStorage(app);
20
+
21
+ // Initialize FractalQ with Firebase
22
+ const fractalq = new FractalQ({
23
+ apiKey: process.env.FRACTALQ_API_KEY,
24
+ firebaseStorage: storage
25
+ });
26
+
27
+ async function upscaleImage(imageFile) {
28
+ try {
29
+ // Upscale and upload in one step
30
+ const uploadedUrl = await fractalq.upscaleAndUpload(imageFile, {
31
+ uploadPath: 'images/upscaled',
32
+ fileName: 'result.jpg',
33
+ onProgress: (progress) => {
34
+ console.log(`${progress.step}: ${progress.progress}%`);
35
+ }
36
+ });
37
+
38
+ console.log('Upscaled image URL:', uploadedUrl);
39
+ return uploadedUrl;
40
+ } catch (error) {
41
+ console.error('Upscaling failed:', error);
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ // Example usage
47
+ // upscaleImage(myImageFile);
48
+
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pipeline Processing Example
3
+ *
4
+ * Example showing how to chain multiple AI operations
5
+ */
6
+
7
+ import FractalQ from '@fractalq/client';
8
+
9
+ const fractalq = new FractalQ({
10
+ apiKey: process.env.FRACTALQ_API_KEY
11
+ });
12
+
13
+ async function processImagePipeline(imageFile) {
14
+ try {
15
+ // Define processing pipeline
16
+ const result = await fractalq.executePipeline([
17
+ {
18
+ nodeId: 'upscale_2x',
19
+ inputs: { quality: 'high' }
20
+ },
21
+ {
22
+ nodeId: 'enhance_colors',
23
+ transform: (data) => ({
24
+ // Transform data between steps if needed
25
+ input_0: data.output_0
26
+ })
27
+ },
28
+ {
29
+ nodeId: 'apply_filter',
30
+ inputs: { filterType: 'sharpen', strength: 0.8 }
31
+ }
32
+ ], {
33
+ initialData: { input_0: imageFile },
34
+ onProgress: ({ step, progress, nodeId }) => {
35
+ console.log(`Processing ${nodeId}: ${progress}%`);
36
+ }
37
+ });
38
+
39
+ console.log('Pipeline complete!');
40
+ console.log('Final result:', result.data.output_0);
41
+
42
+ return result.data;
43
+ } catch (error) {
44
+ console.error('Pipeline failed:', error);
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ // Example usage
50
+ // processImagePipeline(myImageFile);
51
+
@@ -0,0 +1,97 @@
1
+ /**
2
+ * React Component Example
3
+ *
4
+ * Example showing how to use FractalQ in a React component
5
+ */
6
+
7
+ import React, { useState } from 'react';
8
+ import { useFractalQ } from '@fractalq/client/useFractalQ';
9
+
10
+ function ImageProcessor() {
11
+ const [selectedImage, setSelectedImage] = useState(null);
12
+ const [processedImage, setProcessedImage] = useState(null);
13
+
14
+ const { execute, loading, error, reset } = useFractalQ({
15
+ apiKey: process.env.NEXT_PUBLIC_FRACTALQ_API_KEY
16
+ });
17
+
18
+ const handleFileChange = (event) => {
19
+ const file = event.target.files[0];
20
+ if (file) {
21
+ setSelectedImage(file);
22
+ setProcessedImage(null);
23
+ reset();
24
+ }
25
+ };
26
+
27
+ const handleProcess = async () => {
28
+ if (!selectedImage) return;
29
+
30
+ try {
31
+ const result = await execute('upscale_node_id', {
32
+ input_0: selectedImage
33
+ });
34
+
35
+ setProcessedImage(result.data.output_0);
36
+ } catch (err) {
37
+ console.error('Processing failed:', err);
38
+ }
39
+ };
40
+
41
+ return (
42
+ <div className="image-processor">
43
+ <h2>Image Upscaler</h2>
44
+
45
+ <div className="upload-section">
46
+ <input
47
+ type="file"
48
+ accept="image/*"
49
+ onChange={handleFileChange}
50
+ disabled={loading}
51
+ />
52
+ </div>
53
+
54
+ {selectedImage && (
55
+ <div className="preview">
56
+ <h3>Original Image</h3>
57
+ <img
58
+ src={URL.createObjectURL(selectedImage)}
59
+ alt="Original"
60
+ style={{ maxWidth: '400px' }}
61
+ />
62
+ </div>
63
+ )}
64
+
65
+ <button
66
+ onClick={handleProcess}
67
+ disabled={!selectedImage || loading}
68
+ className="process-button"
69
+ >
70
+ {loading ? 'Processing...' : 'Upscale Image'}
71
+ </button>
72
+
73
+ {error && (
74
+ <div className="error-message">
75
+ Error: {error.message}
76
+ </div>
77
+ )}
78
+
79
+ {processedImage && (
80
+ <div className="result">
81
+ <h3>Processed Image</h3>
82
+ <img
83
+ src={processedImage}
84
+ alt="Processed"
85
+ style={{ maxWidth: '800px' }}
86
+ />
87
+ <a href={processedImage} download>
88
+ Download Result
89
+ </a>
90
+ </div>
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export default ImageProcessor;
97
+
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Local smoke test (no secrets). Run from fractalq-client/: npm run test:smoke
3
+ */
4
+ import { getSignalQBaseUrl, getFractalQApiKey, getSignalQApiUrl } from '../src/config/index.js';
5
+
6
+ const base = getSignalQBaseUrl();
7
+ if (base !== 'https://fractalq.com' && !base.startsWith('http')) {
8
+ console.error('FAIL: getSignalQBaseUrl');
9
+ process.exit(1);
10
+ }
11
+
12
+ try {
13
+ getFractalQApiKey('');
14
+ console.error('FAIL: getFractalQApiKey should throw on empty');
15
+ process.exit(1);
16
+ } catch (_) {
17
+ // expected
18
+ }
19
+
20
+ const url = getSignalQApiUrl('testkey123');
21
+ if (!url.includes('/api/node/api/testkey123/execute')) {
22
+ console.error('FAIL: getSignalQApiUrl', url);
23
+ process.exit(1);
24
+ }
25
+
26
+ console.log('OK @fractalq/client config smoke');
package/index.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ export interface FractalQClientOptions {
2
+ useBackendProxy?: boolean;
3
+ apiKey?: string;
4
+ nodeServiceApiKey?: string;
5
+ sessionApiKey?: string;
6
+ /**
7
+ * Proxy mode: optional; defaults to `/api/fractalq`.
8
+ * Direct mode (`useBackendProxy: false`): required unless the host sets `FRACTALQ_NODE_BASE_URL`
9
+ * or `NEXT_PUBLIC_FRACTALQ_BASE_URL`. The library does not embed a default API host.
10
+ */
11
+ baseUrl?: string;
12
+ timeout?: number;
13
+ firebaseStorage?: unknown;
14
+ firebaseConfig?: unknown;
15
+ authTokenProvider?: () => Promise<string | null | undefined>;
16
+ }
17
+
18
+ export function getSignalQBaseUrl(override?: string): string;
19
+ export function getFractalQApiKey(apiKey: string): string;
20
+ export function getSignalQApiUrl(apiKey: string, baseUrl?: string): string;
21
+ export function getApiKeyBySessionId(
22
+ sessionId: string,
23
+ opts?: { accountApiKey?: string; baseUrl?: string }
24
+ ): Promise<string>;
25
+ export function getSignalQApiUrlBySessionId(
26
+ sessionId: string,
27
+ opts?: { accountApiKey?: string; baseUrl?: string }
28
+ ): Promise<string>;
29
+
30
+ export interface ExecuteNodeOptions {
31
+ timeout?: number;
32
+ callbackUrl?: string;
33
+ async?: boolean;
34
+ /**
35
+ * Optional label for this call before the server responds (e.g. temp file namespaces).
36
+ * The authoritative job id comes from FractalQ in `FractalQResponse.jobId` and in callbacks.
37
+ */
38
+ jobId?: string;
39
+ sessionApiKey?: string;
40
+ fractalqVersion?: string;
41
+ useTempFiles?: boolean;
42
+ onProgress?: (p: unknown) => void;
43
+ [key: string]: unknown;
44
+ }
45
+
46
+ export interface FractalQResponse {
47
+ success: boolean;
48
+ async?: boolean;
49
+ /** Job / request id issued by FractalQ (SignalQ) for tracking this run. */
50
+ jobId?: string;
51
+ message?: string;
52
+ data?: Record<string, unknown>;
53
+ error?: string;
54
+ }
55
+
56
+ export class FractalQClient {
57
+ constructor(options?: FractalQClientOptions);
58
+ execute(
59
+ nodeId: string | null,
60
+ inputs?: Record<string, unknown>,
61
+ options?: ExecuteNodeOptions
62
+ ): Promise<FractalQResponse>;
63
+ executeNode(
64
+ nodeId: string | null,
65
+ inputs?: Record<string, unknown>,
66
+ options?: ExecuteNodeOptions
67
+ ): Promise<FractalQResponse>;
68
+ }
69
+
70
+ export class FractalQ extends FractalQClient {
71
+ upscaleAndUpload(
72
+ image: File | Blob | string,
73
+ options?: Record<string, unknown>
74
+ ): Promise<string | Record<string, unknown>>;
75
+ executePipeline(
76
+ nodePipeline: Array<Record<string, unknown>>,
77
+ options?: Record<string, unknown>
78
+ ): Promise<{ success: boolean; data?: unknown }>;
79
+ }
80
+
81
+ export default FractalQ;
package/index.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fractalq/client — public API (implementation lives under src/**).
3
+ */
4
+ import FractalQClient from './src/client/FractalQClient.js';
5
+ import PipelineProcessor from './src/pipeline/index.js';
6
+
7
+ export { FractalQClient };
8
+
9
+ export class FractalQ extends FractalQClient {
10
+ constructor(options = {}) {
11
+ super(options);
12
+ /** @type {import('./src/image/ImageProcessor.js').ImageProcessor | null} */
13
+ this._imageProcessor = null;
14
+ this._pipelineProcessor = new PipelineProcessor(this);
15
+ }
16
+
17
+ async _ensureImageProcessor() {
18
+ if (!this._imageProcessor) {
19
+ const { ImageProcessor } = await import('./src/image/index.js');
20
+ this._imageProcessor = new ImageProcessor(this);
21
+ }
22
+ return this._imageProcessor;
23
+ }
24
+
25
+ async upscaleAndUpload(image, options = {}) {
26
+ const ip = await this._ensureImageProcessor();
27
+ return ip.upscaleAndUpload(image, options);
28
+ }
29
+
30
+ executePipeline(nodePipeline, options = {}) {
31
+ return this._pipelineProcessor.execute(nodePipeline, options);
32
+ }
33
+ }
34
+
35
+ export {
36
+ getSignalQBaseUrl,
37
+ getFractalQApiKey,
38
+ getSignalQApiUrl,
39
+ getApiKeyBySessionId,
40
+ getSignalQApiUrlBySessionId,
41
+ } from './src/config/index.js';
42
+
43
+ export { default as config } from './src/config/index.js';
44
+
45
+ export default FractalQ;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@fractalq/client",
3
+ "version": "2.1.1",
4
+ "description": "FractalQ JavaScript client — pass API keys from your app; this package never reads secret env vars (npm-safe).",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "module": "./index.js",
8
+ "types": "./index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "import": "./index.js",
13
+ "default": "./index.js"
14
+ },
15
+ "./config": {
16
+ "types": "./index.d.ts",
17
+ "import": "./src/config/index.js",
18
+ "default": "./src/config/index.js"
19
+ },
20
+ "./useFractalQ": {
21
+ "types": "./useFractalQ.d.ts",
22
+ "import": "./useFractalQ.js",
23
+ "default": "./useFractalQ.js"
24
+ }
25
+ },
26
+ "peerDependencies": {
27
+ "react": ">=17.0.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "react": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "files": [
35
+ "index.js",
36
+ "index.d.ts",
37
+ "useFractalQ.js",
38
+ "useFractalQ.d.ts",
39
+ "src/**/*.js",
40
+ "examples",
41
+ "LICENSE",
42
+ "README.md"
43
+ ],
44
+ "keywords": [
45
+ "fractalq",
46
+ "api-client",
47
+ "image-processing",
48
+ "typescript"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/fractalq/fractalq-client.git"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "registry": "https://registry.npmjs.org/"
58
+ },
59
+ "engines": {
60
+ "node": ">=18"
61
+ },
62
+ "scripts": {
63
+ "test:smoke": "node examples/smoke-config.mjs"
64
+ }
65
+ }