@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -3
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +377 -0
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +15 -15
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +38 -0
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +1 -1
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,25 @@ All notable changes to the Fluent Commerce Connect SDK will be documented in thi
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
## [0.1.54] - 2026-02-20
|
|
9
|
+
|
|
10
|
+
### Added - Fluent CLI Profile Integration
|
|
11
|
+
- Added `createClientFromProfile(profileName, options?)` factory function to create a `FluentClient` directly from Fluent CLI profiles stored at `~/.fluentcommerce/`
|
|
12
|
+
- Added `loadFluentProfile(profileName, options?)` to read CLI profile files and return a `FluentClientConfig`
|
|
13
|
+
- Added `listFluentProfiles(options?)` to discover available CLI profiles
|
|
14
|
+
- Added `getFluentProfileInfo(profileName, options?)` to inspect profile metadata without loading credentials
|
|
15
|
+
- Supports full retailer loading chain: `profile.json` -> `retailer.<REF>.json` -> `user.<name>.json`
|
|
16
|
+
- When `options.retailer` is specified, reads the retailer file to get `retailerId` and the retailer-specific user override
|
|
17
|
+
- Ensures correct credentials are loaded for multi-retailer accounts (e.g., different users per retailer)
|
|
18
|
+
- Profile directory resolution: `options.profileDir` > `FLUENT_PROFILE_DIR` env var > `~/.fluentcommerce/`
|
|
19
|
+
- New types exported: `FluentProfileOptions`, `FluentProfileInfo`
|
|
20
|
+
- Skips hidden/internal directories (e.g., `.sessions`) when listing profiles
|
|
21
|
+
|
|
22
|
+
### Fixed - Fluent CLI Profile Integration Test Stability
|
|
23
|
+
- Unit tests use real temporary filesystem fixtures instead of platform-sensitive path mocks for cross-platform reliability.
|
|
24
|
+
|
|
25
|
+
### Added - Fluent CLI Profile Integration Documentation
|
|
26
|
+
- README section with usage examples for `createClientFromProfile()` and `listFluentProfiles()`, including multi-retailer behavior.
|
|
11
27
|
|
|
12
28
|
## [0.1.53] - 2026-02-15
|
|
13
29
|
|
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ TypeScript SDK for building **Fluent Commerce** integrations across Node.js, Den
|
|
|
31
31
|
- [More Examples](#-more-quick-examples) - Common patterns
|
|
32
32
|
- [Core Services](#core-services) - API reference
|
|
33
33
|
- [CLI Tools](#cli-tooling) - Command-line utilities
|
|
34
|
+
- [Standalone Usage](#-standalone-usage-outside-versori) - Node.js/Deno app integration
|
|
35
|
+
- [CLI Profile Integration](#fluent-cli-profile-integration-no-hardcoded-credentials) - Reuse Fluent CLI profiles
|
|
34
36
|
- [Authentication & Webhooks](#authentication-webhooks) - Security
|
|
35
37
|
- [Security & Compliance](#security--compliance) - Security audits, SOC 2, vulnerability management 🆕
|
|
36
38
|
- [Common Pitfalls](#common-pitfalls-and-fixes) - Troubleshooting
|
|
@@ -1668,6 +1670,43 @@ console.log('Status:', response.status);
|
|
|
1668
1670
|
console.log('Data:', response.data);
|
|
1669
1671
|
```
|
|
1670
1672
|
|
|
1673
|
+
### Fluent CLI Profile Integration (No Hardcoded Credentials)
|
|
1674
|
+
|
|
1675
|
+
If you already use the Fluent CLI (`fluent profile create`, `fluent profile switch`), you can reuse the same profile files directly in the SDK.
|
|
1676
|
+
|
|
1677
|
+
```typescript
|
|
1678
|
+
import { createClientFromProfile, listFluentProfiles } from '@fluentcommerce/fc-connect-sdk';
|
|
1679
|
+
|
|
1680
|
+
// Discover available local Fluent CLI profiles (~/.fluentcommerce/*)
|
|
1681
|
+
const profiles = listFluentProfiles();
|
|
1682
|
+
// Example: ['PROFILE_ALPHA', 'PROFILE_BETA']
|
|
1683
|
+
|
|
1684
|
+
// Create client from profile.json + user.*.json
|
|
1685
|
+
const client = await createClientFromProfile('PROFILE_ALPHA');
|
|
1686
|
+
|
|
1687
|
+
// Multi-retailer profile: pass retailer ref to load retailerId + retailer user override
|
|
1688
|
+
const retailerClient = await createClientFromProfile('PROFILE_BETA', {
|
|
1689
|
+
retailer: 'RETAILER_ALPHA',
|
|
1690
|
+
validateConnection: true, // Optional fail-fast auth check
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
const events = await retailerClient.getEvents({
|
|
1694
|
+
eventType: 'ORCHESTRATION_AUDIT',
|
|
1695
|
+
'context.rootEntityType': 'ORDER',
|
|
1696
|
+
});
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
**Behavior and limitations:**
|
|
1700
|
+
|
|
1701
|
+
- Uses Fluent CLI profile files from `~/.fluentcommerce/<PROFILE>/`:
|
|
1702
|
+
- `profile.json` (base URL, client ID/secret, default user)
|
|
1703
|
+
- `retailer.<REF>.json` (optional retailer ID + retailer-specific user override)
|
|
1704
|
+
- `user.<name>.json` (username/password)
|
|
1705
|
+
- For multi-retailer setups, pass `retailer` to ensure the correct retailer-specific user credentials are loaded.
|
|
1706
|
+
- Throws clear errors when profile directories/files are missing or malformed.
|
|
1707
|
+
- Supports profile directory overrides via `FLUENT_PROFILE_DIR` or `options.profileDir`.
|
|
1708
|
+
- Intended for standalone Node.js/Deno usage; in Versori workflows use `createClient(ctx)` instead.
|
|
1709
|
+
|
|
1671
1710
|
### What's Handled Automatically
|
|
1672
1711
|
|
|
1673
1712
|
- ✅ **OAuth2 Authentication** - Token fetched and cached automatically
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { loadFluentProfile, listFluentProfiles, getFluentProfileInfo, resolveProfileBaseDir, } from './profile-loader';
|
|
2
|
+
export type { FluentProfileOptions, FluentProfileInfo } from './profile-loader';
|
|
3
|
+
export { OAuth2AuthProvider, BearerTokenAuthProvider, AuthManager, VersoriConnectionAdapter, type VersoriConnection, } from './auth-provider';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VersoriConnectionAdapter = exports.AuthManager = exports.BearerTokenAuthProvider = exports.OAuth2AuthProvider = exports.resolveProfileBaseDir = exports.getFluentProfileInfo = exports.listFluentProfiles = exports.loadFluentProfile = void 0;
|
|
4
|
+
var profile_loader_1 = require("./profile-loader");
|
|
5
|
+
Object.defineProperty(exports, "loadFluentProfile", { enumerable: true, get: function () { return profile_loader_1.loadFluentProfile; } });
|
|
6
|
+
Object.defineProperty(exports, "listFluentProfiles", { enumerable: true, get: function () { return profile_loader_1.listFluentProfiles; } });
|
|
7
|
+
Object.defineProperty(exports, "getFluentProfileInfo", { enumerable: true, get: function () { return profile_loader_1.getFluentProfileInfo; } });
|
|
8
|
+
Object.defineProperty(exports, "resolveProfileBaseDir", { enumerable: true, get: function () { return profile_loader_1.resolveProfileBaseDir; } });
|
|
9
|
+
var auth_provider_1 = require("./auth-provider");
|
|
10
|
+
Object.defineProperty(exports, "OAuth2AuthProvider", { enumerable: true, get: function () { return auth_provider_1.OAuth2AuthProvider; } });
|
|
11
|
+
Object.defineProperty(exports, "BearerTokenAuthProvider", { enumerable: true, get: function () { return auth_provider_1.BearerTokenAuthProvider; } });
|
|
12
|
+
Object.defineProperty(exports, "AuthManager", { enumerable: true, get: function () { return auth_provider_1.AuthManager; } });
|
|
13
|
+
Object.defineProperty(exports, "VersoriConnectionAdapter", { enumerable: true, get: function () { return auth_provider_1.VersoriConnectionAdapter; } });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FluentClientConfig } from '../types';
|
|
2
|
+
export interface FluentProfileOptions {
|
|
3
|
+
profileDir?: string;
|
|
4
|
+
retailer?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
retryConfig?: FluentClientConfig['retryConfig'];
|
|
7
|
+
}
|
|
8
|
+
export interface FluentProfileInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
accountId: string;
|
|
12
|
+
username: string;
|
|
13
|
+
retailers: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveProfileBaseDir(options?: FluentProfileOptions): string;
|
|
16
|
+
export declare function loadFluentProfile(profileName: string, options?: FluentProfileOptions): FluentClientConfig;
|
|
17
|
+
export declare function listFluentProfiles(options?: FluentProfileOptions): string[];
|
|
18
|
+
export declare function getFluentProfileInfo(profileName: string, options?: FluentProfileOptions): FluentProfileInfo | null;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveProfileBaseDir = resolveProfileBaseDir;
|
|
37
|
+
exports.loadFluentProfile = loadFluentProfile;
|
|
38
|
+
exports.listFluentProfiles = listFluentProfiles;
|
|
39
|
+
exports.getFluentProfileInfo = getFluentProfileInfo;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
function resolveProfileBaseDir(options) {
|
|
44
|
+
if (options?.profileDir) {
|
|
45
|
+
return options.profileDir;
|
|
46
|
+
}
|
|
47
|
+
const envDir = process.env.FLUENT_PROFILE_DIR;
|
|
48
|
+
if (envDir) {
|
|
49
|
+
return envDir;
|
|
50
|
+
}
|
|
51
|
+
return path.join(os.homedir(), '.fluentcommerce');
|
|
52
|
+
}
|
|
53
|
+
function loadFluentProfile(profileName, options) {
|
|
54
|
+
if (!profileName || typeof profileName !== 'string') {
|
|
55
|
+
throw new Error('Profile name is required');
|
|
56
|
+
}
|
|
57
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
58
|
+
const profileDir = path.join(baseDir, profileName);
|
|
59
|
+
if (!fs.existsSync(profileDir)) {
|
|
60
|
+
const available = listFluentProfiles(options);
|
|
61
|
+
throw new Error(`Fluent CLI profile '${profileName}' not found at ${profileDir}. ` +
|
|
62
|
+
(available.length > 0
|
|
63
|
+
? `Available profiles: ${available.join(', ')}`
|
|
64
|
+
: `No profiles found in ${baseDir}. Create one with: fluent profile create ${profileName}`));
|
|
65
|
+
}
|
|
66
|
+
const profileJsonPath = path.join(profileDir, 'profile.json');
|
|
67
|
+
if (!fs.existsSync(profileJsonPath)) {
|
|
68
|
+
throw new Error(`profile.json not found in ${profileDir}. ` +
|
|
69
|
+
`This directory may not be a valid Fluent CLI profile.`);
|
|
70
|
+
}
|
|
71
|
+
let profile;
|
|
72
|
+
try {
|
|
73
|
+
const raw = fs.readFileSync(profileJsonPath, 'utf-8');
|
|
74
|
+
profile = JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new Error(`Failed to read ${profileJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
if (!profile.baseUrl) {
|
|
80
|
+
throw new Error(`Profile '${profileName}' is missing required field 'baseUrl' in profile.json`);
|
|
81
|
+
}
|
|
82
|
+
if (!profile.id) {
|
|
83
|
+
throw new Error(`Profile '${profileName}' is missing required field 'id' in profile.json`);
|
|
84
|
+
}
|
|
85
|
+
if (!profile.clientSecret) {
|
|
86
|
+
throw new Error(`Profile '${profileName}' is missing required field 'clientSecret' in profile.json`);
|
|
87
|
+
}
|
|
88
|
+
if (!profile.user) {
|
|
89
|
+
throw new Error(`Profile '${profileName}' is missing required field 'user' in profile.json`);
|
|
90
|
+
}
|
|
91
|
+
let retailerId;
|
|
92
|
+
let resolvedUser = profile.user;
|
|
93
|
+
if (options?.retailer) {
|
|
94
|
+
const retailerJsonPath = path.join(profileDir, `retailer.${options.retailer}.json`);
|
|
95
|
+
if (!fs.existsSync(retailerJsonPath)) {
|
|
96
|
+
const availableRetailers = listProfileRetailers(profileDir);
|
|
97
|
+
throw new Error(`Retailer '${options.retailer}' not found for profile '${profileName}' at ${retailerJsonPath}. ` +
|
|
98
|
+
(availableRetailers.length > 0
|
|
99
|
+
? `Available retailers: ${availableRetailers.join(', ')}`
|
|
100
|
+
: `No retailer files found in ${profileDir}.`));
|
|
101
|
+
}
|
|
102
|
+
let retailer;
|
|
103
|
+
try {
|
|
104
|
+
const raw = fs.readFileSync(retailerJsonPath, 'utf-8');
|
|
105
|
+
retailer = JSON.parse(raw);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new Error(`Failed to read ${retailerJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
+
}
|
|
110
|
+
retailerId = retailer.id;
|
|
111
|
+
if (retailer.user) {
|
|
112
|
+
resolvedUser = retailer.user;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const userJsonPath = path.join(profileDir, `user.${resolvedUser}.json`);
|
|
116
|
+
if (!fs.existsSync(userJsonPath)) {
|
|
117
|
+
throw new Error(`User credentials file not found: ${userJsonPath}. ` +
|
|
118
|
+
`Expected file for user '${resolvedUser}'` +
|
|
119
|
+
(options?.retailer
|
|
120
|
+
? ` (set by retailer '${options.retailer}').`
|
|
121
|
+
: ` (set in profile.json).`));
|
|
122
|
+
}
|
|
123
|
+
let userCreds;
|
|
124
|
+
try {
|
|
125
|
+
const raw = fs.readFileSync(userJsonPath, 'utf-8');
|
|
126
|
+
userCreds = JSON.parse(raw);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw new Error(`Failed to read ${userJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
|
+
}
|
|
131
|
+
if (!userCreds.username || !userCreds.password) {
|
|
132
|
+
throw new Error(`User credentials file ${userJsonPath} is missing 'username' or 'password' field`);
|
|
133
|
+
}
|
|
134
|
+
const config = {
|
|
135
|
+
baseUrl: profile.baseUrl,
|
|
136
|
+
clientId: profile.id,
|
|
137
|
+
clientSecret: profile.clientSecret,
|
|
138
|
+
username: userCreds.username,
|
|
139
|
+
password: userCreds.password,
|
|
140
|
+
};
|
|
141
|
+
if (retailerId) {
|
|
142
|
+
config.retailerId = retailerId;
|
|
143
|
+
}
|
|
144
|
+
if (options?.timeout) {
|
|
145
|
+
config.timeout = options.timeout;
|
|
146
|
+
}
|
|
147
|
+
if (options?.retryConfig) {
|
|
148
|
+
config.retryConfig = options.retryConfig;
|
|
149
|
+
}
|
|
150
|
+
return config;
|
|
151
|
+
}
|
|
152
|
+
function listFluentProfiles(options) {
|
|
153
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
154
|
+
if (!fs.existsSync(baseDir)) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
159
|
+
return entries
|
|
160
|
+
.filter(entry => {
|
|
161
|
+
if (!entry.isDirectory())
|
|
162
|
+
return false;
|
|
163
|
+
if (entry.name.startsWith('.'))
|
|
164
|
+
return false;
|
|
165
|
+
const profileJson = path.join(baseDir, entry.name, 'profile.json');
|
|
166
|
+
return fs.existsSync(profileJson);
|
|
167
|
+
})
|
|
168
|
+
.map(entry => entry.name)
|
|
169
|
+
.sort();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function getFluentProfileInfo(profileName, options) {
|
|
176
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
177
|
+
const profileDirPath = path.join(baseDir, profileName);
|
|
178
|
+
const profileJsonPath = path.join(profileDirPath, 'profile.json');
|
|
179
|
+
if (!fs.existsSync(profileJsonPath)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const raw = fs.readFileSync(profileJsonPath, 'utf-8');
|
|
184
|
+
const profile = JSON.parse(raw);
|
|
185
|
+
return {
|
|
186
|
+
name: profileName,
|
|
187
|
+
baseUrl: profile.baseUrl,
|
|
188
|
+
accountId: profile.id,
|
|
189
|
+
username: profile.user,
|
|
190
|
+
retailers: listProfileRetailers(profileDirPath),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function listProfileRetailers(profileDirPath) {
|
|
198
|
+
try {
|
|
199
|
+
const entries = fs.readdirSync(profileDirPath);
|
|
200
|
+
return entries
|
|
201
|
+
.filter(name => name.startsWith('retailer.') && name.endsWith('.json'))
|
|
202
|
+
.map(name => name.slice('retailer.'.length, -'.json'.length))
|
|
203
|
+
.sort();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FluentClient } from './clients/fluent-client';
|
|
2
2
|
import { FluentVersoriClient } from './versori/fluent-versori-client';
|
|
3
3
|
import { FluentClientConfig, Logger, ExecutionContext } from './types';
|
|
4
|
+
import { type FluentProfileOptions } from './auth/profile-loader';
|
|
4
5
|
export type ClientContext = {
|
|
5
6
|
fetch: typeof fetch;
|
|
6
7
|
log?: Logger;
|
|
@@ -24,3 +25,6 @@ export declare const isDirectContext: (context: ClientContext) => context is {
|
|
|
24
25
|
config: FluentClientConfig;
|
|
25
26
|
logger?: Logger;
|
|
26
27
|
};
|
|
28
|
+
export declare function createClientFromProfile(profileName: string, options?: FluentProfileOptions & CreateClientOptions & {
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
}): Promise<FluentClient>;
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isDirectContext = exports.isHttpContext = void 0;
|
|
4
4
|
exports.createClient = createClient;
|
|
5
|
+
exports.createClientFromProfile = createClientFromProfile;
|
|
5
6
|
const fluent_client_1 = require("./clients/fluent-client");
|
|
6
7
|
const fluent_versori_client_1 = require("./versori/fluent-versori-client");
|
|
8
|
+
const profile_loader_1 = require("./auth/profile-loader");
|
|
7
9
|
async function createClient(context, options) {
|
|
8
10
|
const logger = 'log' in context ? context.log : 'logger' in context ? context.logger : undefined;
|
|
9
11
|
const validateConnection = options?.validateConnection ?? false;
|
|
@@ -107,3 +109,11 @@ const isDirectContext = (context) => {
|
|
|
107
109
|
return 'config' in context;
|
|
108
110
|
};
|
|
109
111
|
exports.isDirectContext = isDirectContext;
|
|
112
|
+
async function createClientFromProfile(profileName, options) {
|
|
113
|
+
const config = (0, profile_loader_1.loadFluentProfile)(profileName, options);
|
|
114
|
+
const client = new fluent_client_1.FluentClient(config, options?.logger);
|
|
115
|
+
if (options?.validateConnection) {
|
|
116
|
+
await client.graphql({ query: 'query { me { ref } }' });
|
|
117
|
+
}
|
|
118
|
+
return client;
|
|
119
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createClient, isHttpContext, isDirectContext } from './client-factory';
|
|
1
|
+
export { createClient, createClientFromProfile, isHttpContext, isDirectContext, } from './client-factory';
|
|
2
2
|
export type { ClientContext, CreateClientOptions } from './client-factory';
|
|
3
3
|
export { FluentClient } from './clients/fluent-client';
|
|
4
4
|
export { FluentVersoriClient } from './versori/fluent-versori-client';
|
|
@@ -14,6 +14,8 @@ export { DataSourceType } from './types';
|
|
|
14
14
|
export { parseAndValidateCsv, detectFileType, parseWebhookRequest, validateFluentEvent, validateGraphQLPayload, generateFileName, generateTimestampedFileName, generateFilePath, generateDateSubdirectories, extractFileName, extractFileExtension, replaceFileExtension, type FileNamingOptions, } from './utils';
|
|
15
15
|
export { detectPaginationVariables, extractConnection, extractCursor, hasMorePages, buildPaginationVariables, type PaginationVariables, } from './utils/pagination-helpers';
|
|
16
16
|
export { OAuth2AuthProvider, BearerTokenAuthProvider, AuthManager, VersoriConnectionAdapter, type VersoriConnection, } from './auth/auth-provider';
|
|
17
|
+
export { loadFluentProfile, listFluentProfiles, getFluentProfileInfo, resolveProfileBaseDir, } from './auth/profile-loader';
|
|
18
|
+
export type { FluentProfileOptions, FluentProfileInfo } from './auth/profile-loader';
|
|
17
19
|
export { type FileDiscoveryStrategy, type FileDiscoveryContext, type FileParserStrategy, type TransformationStrategy, type TransformationContext, type IngestionValidationResult, type BatchSubmissionStrategy, type BatchSubmissionContext, type BatchSubmissionResult, type JobManagementStrategy, type JobContext, type JobStatus, type IngestionStrategy, type StrategyFactory, } from './services/orchestration/strategies/ingestion-strategies';
|
|
18
20
|
export * from './errors/index';
|
|
19
21
|
export { ConfigurationError, AggregateIngestionError, IngestionErrorFactory, } from './errors/ingestion-errors';
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,10 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
exports.VERSION = exports.S3ServiceError = exports.S3Service = exports.FluentConnectionTester = exports.S3ComparisonTester = exports.S3PresignedTester = exports.S3SDKTester = exports.ProcessingStatus = exports.ValidationMode = exports.AwsRegion = exports.EntityType = exports.BatchAction = exports.FileType = exports.JobStrategy = exports.FileEncoding = exports.CsvDelimiter = exports.ResolverError = exports.MappingError = exports.GraphQLTemplateGenerator = exports.GraphQLIntrospectionService = exports.formatErrorSummary = exports.getSuccessfulMutations = exports.getFailedMutations = exports.getErrorMessage = exports.parseAliasedMutationResponse = exports.inferInputTypeName = exports.buildAliasedMutationQuery = exports.buildMutationQuery = exports.GraphQLMutationMapper = exports.createResolverBuilder = exports.ResolverBuilder = exports.getSdkResolverNames = exports.isSdkResolver = exports.getSdkResolver = exports.sdkResolvers = exports.UniversalMapper = exports.UniversalPathResolver = exports.JSONBuilder = exports.CSVBuilder = void 0;
|
|
17
|
+
exports.JSONParserService = exports.XMLParserService = exports.ParquetParserService = exports.CSVParserService = exports.classifyErrors = exports.classifyError = exports.IngestionErrorFactory = exports.AggregateIngestionError = exports.ConfigurationError = exports.resolveProfileBaseDir = exports.getFluentProfileInfo = exports.listFluentProfiles = exports.loadFluentProfile = exports.VersoriConnectionAdapter = exports.AuthManager = exports.BearerTokenAuthProvider = exports.OAuth2AuthProvider = exports.buildPaginationVariables = exports.hasMorePages = exports.extractCursor = exports.extractConnection = exports.detectPaginationVariables = exports.replaceFileExtension = exports.extractFileExtension = exports.extractFileName = exports.generateDateSubdirectories = exports.generateFilePath = exports.generateTimestampedFileName = exports.generateFileName = exports.validateGraphQLPayload = exports.validateFluentEvent = exports.parseWebhookRequest = exports.detectFileType = exports.parseAndValidateCsv = exports.DataSourceType = exports.Intermediate = exports.SchemaValidationService = exports.SignatureAlgorithm = exports.WebhookValidationError = exports.WebhookValidationFactory = exports.WebhookValidationService = exports.VersoriIndexedFileTracker = exports.VersoriKVAdapter = exports.VersoriFileTracker = exports.FluentVersoriClient = exports.FluentClient = exports.isDirectContext = exports.isHttpContext = exports.createClientFromProfile = exports.createClient = void 0;
|
|
18
|
+
exports.VERSION = exports.S3ServiceError = exports.S3Service = exports.FluentConnectionTester = exports.S3ComparisonTester = exports.S3PresignedTester = exports.S3SDKTester = exports.ProcessingStatus = exports.ValidationMode = exports.AwsRegion = exports.EntityType = exports.BatchAction = exports.FileType = exports.JobStrategy = exports.FileEncoding = exports.CsvDelimiter = exports.ResolverError = exports.MappingError = exports.GraphQLTemplateGenerator = exports.GraphQLIntrospectionService = exports.formatErrorSummary = exports.getSuccessfulMutations = exports.getFailedMutations = exports.getErrorMessage = exports.parseAliasedMutationResponse = exports.inferInputTypeName = exports.buildAliasedMutationQuery = exports.buildMutationQuery = exports.GraphQLMutationMapper = exports.createResolverBuilder = exports.ResolverBuilder = exports.getSdkResolverNames = exports.isSdkResolver = exports.getSdkResolver = exports.sdkResolvers = exports.UniversalMapper = exports.UniversalPathResolver = exports.JSONBuilder = exports.CSVBuilder = exports.XMLBuilder = exports.detectParserFileType = exports.isParserSupported = exports.getSupportedParserTypes = exports.createParser = void 0;
|
|
19
19
|
var client_factory_1 = require("./client-factory");
|
|
20
20
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_factory_1.createClient; } });
|
|
21
|
+
Object.defineProperty(exports, "createClientFromProfile", { enumerable: true, get: function () { return client_factory_1.createClientFromProfile; } });
|
|
21
22
|
Object.defineProperty(exports, "isHttpContext", { enumerable: true, get: function () { return client_factory_1.isHttpContext; } });
|
|
22
23
|
Object.defineProperty(exports, "isDirectContext", { enumerable: true, get: function () { return client_factory_1.isDirectContext; } });
|
|
23
24
|
var fluent_client_1 = require("./clients/fluent-client");
|
|
@@ -66,6 +67,11 @@ Object.defineProperty(exports, "OAuth2AuthProvider", { enumerable: true, get: fu
|
|
|
66
67
|
Object.defineProperty(exports, "BearerTokenAuthProvider", { enumerable: true, get: function () { return auth_provider_1.BearerTokenAuthProvider; } });
|
|
67
68
|
Object.defineProperty(exports, "AuthManager", { enumerable: true, get: function () { return auth_provider_1.AuthManager; } });
|
|
68
69
|
Object.defineProperty(exports, "VersoriConnectionAdapter", { enumerable: true, get: function () { return auth_provider_1.VersoriConnectionAdapter; } });
|
|
70
|
+
var profile_loader_1 = require("./auth/profile-loader");
|
|
71
|
+
Object.defineProperty(exports, "loadFluentProfile", { enumerable: true, get: function () { return profile_loader_1.loadFluentProfile; } });
|
|
72
|
+
Object.defineProperty(exports, "listFluentProfiles", { enumerable: true, get: function () { return profile_loader_1.listFluentProfiles; } });
|
|
73
|
+
Object.defineProperty(exports, "getFluentProfileInfo", { enumerable: true, get: function () { return profile_loader_1.getFluentProfileInfo; } });
|
|
74
|
+
Object.defineProperty(exports, "resolveProfileBaseDir", { enumerable: true, get: function () { return profile_loader_1.resolveProfileBaseDir; } });
|
|
69
75
|
__exportStar(require("./errors/index"), exports);
|
|
70
76
|
var ingestion_errors_1 = require("./errors/ingestion-errors");
|
|
71
77
|
Object.defineProperty(exports, "ConfigurationError", { enumerable: true, get: function () { return ingestion_errors_1.ConfigurationError; } });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { loadFluentProfile, listFluentProfiles, getFluentProfileInfo, resolveProfileBaseDir, } from './profile-loader.js';
|
|
2
|
+
export type { FluentProfileOptions, FluentProfileInfo } from './profile-loader.js';
|
|
3
|
+
export { OAuth2AuthProvider, BearerTokenAuthProvider, AuthManager, VersoriConnectionAdapter, type VersoriConnection, } from './auth-provider.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FluentClientConfig } from '../types/index.js';
|
|
2
|
+
export interface FluentProfileOptions {
|
|
3
|
+
profileDir?: string;
|
|
4
|
+
retailer?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
retryConfig?: FluentClientConfig['retryConfig'];
|
|
7
|
+
}
|
|
8
|
+
export interface FluentProfileInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
accountId: string;
|
|
12
|
+
username: string;
|
|
13
|
+
retailers: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function resolveProfileBaseDir(options?: FluentProfileOptions): string;
|
|
16
|
+
export declare function loadFluentProfile(profileName: string, options?: FluentProfileOptions): FluentClientConfig;
|
|
17
|
+
export declare function listFluentProfiles(options?: FluentProfileOptions): string[];
|
|
18
|
+
export declare function getFluentProfileInfo(profileName: string, options?: FluentProfileOptions): FluentProfileInfo | null;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
export function resolveProfileBaseDir(options) {
|
|
5
|
+
if (options?.profileDir) {
|
|
6
|
+
return options.profileDir;
|
|
7
|
+
}
|
|
8
|
+
const envDir = process.env.FLUENT_PROFILE_DIR;
|
|
9
|
+
if (envDir) {
|
|
10
|
+
return envDir;
|
|
11
|
+
}
|
|
12
|
+
return path.join(os.homedir(), '.fluentcommerce');
|
|
13
|
+
}
|
|
14
|
+
export function loadFluentProfile(profileName, options) {
|
|
15
|
+
if (!profileName || typeof profileName !== 'string') {
|
|
16
|
+
throw new Error('Profile name is required');
|
|
17
|
+
}
|
|
18
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
19
|
+
const profileDir = path.join(baseDir, profileName);
|
|
20
|
+
if (!fs.existsSync(profileDir)) {
|
|
21
|
+
const available = listFluentProfiles(options);
|
|
22
|
+
throw new Error(`Fluent CLI profile '${profileName}' not found at ${profileDir}. ` +
|
|
23
|
+
(available.length > 0
|
|
24
|
+
? `Available profiles: ${available.join(', ')}`
|
|
25
|
+
: `No profiles found in ${baseDir}. Create one with: fluent profile create ${profileName}`));
|
|
26
|
+
}
|
|
27
|
+
const profileJsonPath = path.join(profileDir, 'profile.json');
|
|
28
|
+
if (!fs.existsSync(profileJsonPath)) {
|
|
29
|
+
throw new Error(`profile.json not found in ${profileDir}. ` +
|
|
30
|
+
`This directory may not be a valid Fluent CLI profile.`);
|
|
31
|
+
}
|
|
32
|
+
let profile;
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs.readFileSync(profileJsonPath, 'utf-8');
|
|
35
|
+
profile = JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new Error(`Failed to read ${profileJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
39
|
+
}
|
|
40
|
+
if (!profile.baseUrl) {
|
|
41
|
+
throw new Error(`Profile '${profileName}' is missing required field 'baseUrl' in profile.json`);
|
|
42
|
+
}
|
|
43
|
+
if (!profile.id) {
|
|
44
|
+
throw new Error(`Profile '${profileName}' is missing required field 'id' in profile.json`);
|
|
45
|
+
}
|
|
46
|
+
if (!profile.clientSecret) {
|
|
47
|
+
throw new Error(`Profile '${profileName}' is missing required field 'clientSecret' in profile.json`);
|
|
48
|
+
}
|
|
49
|
+
if (!profile.user) {
|
|
50
|
+
throw new Error(`Profile '${profileName}' is missing required field 'user' in profile.json`);
|
|
51
|
+
}
|
|
52
|
+
let retailerId;
|
|
53
|
+
let resolvedUser = profile.user;
|
|
54
|
+
if (options?.retailer) {
|
|
55
|
+
const retailerJsonPath = path.join(profileDir, `retailer.${options.retailer}.json`);
|
|
56
|
+
if (!fs.existsSync(retailerJsonPath)) {
|
|
57
|
+
const availableRetailers = listProfileRetailers(profileDir);
|
|
58
|
+
throw new Error(`Retailer '${options.retailer}' not found for profile '${profileName}' at ${retailerJsonPath}. ` +
|
|
59
|
+
(availableRetailers.length > 0
|
|
60
|
+
? `Available retailers: ${availableRetailers.join(', ')}`
|
|
61
|
+
: `No retailer files found in ${profileDir}.`));
|
|
62
|
+
}
|
|
63
|
+
let retailer;
|
|
64
|
+
try {
|
|
65
|
+
const raw = fs.readFileSync(retailerJsonPath, 'utf-8');
|
|
66
|
+
retailer = JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new Error(`Failed to read ${retailerJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
71
|
+
retailerId = retailer.id;
|
|
72
|
+
if (retailer.user) {
|
|
73
|
+
resolvedUser = retailer.user;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const userJsonPath = path.join(profileDir, `user.${resolvedUser}.json`);
|
|
77
|
+
if (!fs.existsSync(userJsonPath)) {
|
|
78
|
+
throw new Error(`User credentials file not found: ${userJsonPath}. ` +
|
|
79
|
+
`Expected file for user '${resolvedUser}'` +
|
|
80
|
+
(options?.retailer
|
|
81
|
+
? ` (set by retailer '${options.retailer}').`
|
|
82
|
+
: ` (set in profile.json).`));
|
|
83
|
+
}
|
|
84
|
+
let userCreds;
|
|
85
|
+
try {
|
|
86
|
+
const raw = fs.readFileSync(userJsonPath, 'utf-8');
|
|
87
|
+
userCreds = JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
throw new Error(`Failed to read ${userJsonPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
91
|
+
}
|
|
92
|
+
if (!userCreds.username || !userCreds.password) {
|
|
93
|
+
throw new Error(`User credentials file ${userJsonPath} is missing 'username' or 'password' field`);
|
|
94
|
+
}
|
|
95
|
+
const config = {
|
|
96
|
+
baseUrl: profile.baseUrl,
|
|
97
|
+
clientId: profile.id,
|
|
98
|
+
clientSecret: profile.clientSecret,
|
|
99
|
+
username: userCreds.username,
|
|
100
|
+
password: userCreds.password,
|
|
101
|
+
};
|
|
102
|
+
if (retailerId) {
|
|
103
|
+
config.retailerId = retailerId;
|
|
104
|
+
}
|
|
105
|
+
if (options?.timeout) {
|
|
106
|
+
config.timeout = options.timeout;
|
|
107
|
+
}
|
|
108
|
+
if (options?.retryConfig) {
|
|
109
|
+
config.retryConfig = options.retryConfig;
|
|
110
|
+
}
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
export function listFluentProfiles(options) {
|
|
114
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
115
|
+
if (!fs.existsSync(baseDir)) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
120
|
+
return entries
|
|
121
|
+
.filter(entry => {
|
|
122
|
+
if (!entry.isDirectory())
|
|
123
|
+
return false;
|
|
124
|
+
if (entry.name.startsWith('.'))
|
|
125
|
+
return false;
|
|
126
|
+
const profileJson = path.join(baseDir, entry.name, 'profile.json');
|
|
127
|
+
return fs.existsSync(profileJson);
|
|
128
|
+
})
|
|
129
|
+
.map(entry => entry.name)
|
|
130
|
+
.sort();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function getFluentProfileInfo(profileName, options) {
|
|
137
|
+
const baseDir = resolveProfileBaseDir(options);
|
|
138
|
+
const profileDirPath = path.join(baseDir, profileName);
|
|
139
|
+
const profileJsonPath = path.join(profileDirPath, 'profile.json');
|
|
140
|
+
if (!fs.existsSync(profileJsonPath)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const raw = fs.readFileSync(profileJsonPath, 'utf-8');
|
|
145
|
+
const profile = JSON.parse(raw);
|
|
146
|
+
return {
|
|
147
|
+
name: profileName,
|
|
148
|
+
baseUrl: profile.baseUrl,
|
|
149
|
+
accountId: profile.id,
|
|
150
|
+
username: profile.user,
|
|
151
|
+
retailers: listProfileRetailers(profileDirPath),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function listProfileRetailers(profileDirPath) {
|
|
159
|
+
try {
|
|
160
|
+
const entries = fs.readdirSync(profileDirPath);
|
|
161
|
+
return entries
|
|
162
|
+
.filter(name => name.startsWith('retailer.') && name.endsWith('.json'))
|
|
163
|
+
.map(name => name.slice('retailer.'.length, -'.json'.length))
|
|
164
|
+
.sort();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FluentClient } from './clients/fluent-client.js';
|
|
2
2
|
import { FluentVersoriClient } from './versori/fluent-versori-client.js';
|
|
3
3
|
import { FluentClientConfig, Logger, ExecutionContext } from './types/index.js';
|
|
4
|
+
import { type FluentProfileOptions } from './auth/profile-loader.js';
|
|
4
5
|
export type ClientContext = {
|
|
5
6
|
fetch: typeof fetch;
|
|
6
7
|
log?: Logger;
|
|
@@ -24,3 +25,6 @@ export declare const isDirectContext: (context: ClientContext) => context is {
|
|
|
24
25
|
config: FluentClientConfig;
|
|
25
26
|
logger?: Logger;
|
|
26
27
|
};
|
|
28
|
+
export declare function createClientFromProfile(profileName: string, options?: FluentProfileOptions & CreateClientOptions & {
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
}): Promise<FluentClient>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FluentClient } from './clients/fluent-client.js';
|
|
2
2
|
import { FluentVersoriClient } from './versori/fluent-versori-client.js';
|
|
3
|
+
import { loadFluentProfile } from './auth/profile-loader.js';
|
|
3
4
|
export async function createClient(context, options) {
|
|
4
5
|
const logger = 'log' in context ? context.log : 'logger' in context ? context.logger : undefined;
|
|
5
6
|
const validateConnection = options?.validateConnection ?? false;
|
|
@@ -101,3 +102,11 @@ export const isHttpContext = (context) => {
|
|
|
101
102
|
export const isDirectContext = (context) => {
|
|
102
103
|
return 'config' in context;
|
|
103
104
|
};
|
|
105
|
+
export async function createClientFromProfile(profileName, options) {
|
|
106
|
+
const config = loadFluentProfile(profileName, options);
|
|
107
|
+
const client = new FluentClient(config, options?.logger);
|
|
108
|
+
if (options?.validateConnection) {
|
|
109
|
+
await client.graphql({ query: 'query { me { ref } }' });
|
|
110
|
+
}
|
|
111
|
+
return client;
|
|
112
|
+
}
|