@fluentcommerce/fc-connect-sdk 0.1.52 → 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 +530 -506
- package/README.md +52 -3
- 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/cjs/types/index.d.ts +1 -1
- 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/esm/types/index.d.ts +1 -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/dist/types/types/index.d.ts +1 -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 +24 -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/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
|
|
@@ -1309,7 +1311,17 @@ if (logs.results[0]?.id) {
|
|
|
1309
1311
|
|
|
1310
1312
|
#### Query Parameters
|
|
1311
1313
|
|
|
1312
|
-
All parameters are optional. Context filters use
|
|
1314
|
+
All parameters are optional. Context filters use **dot-notation keys** which require quotes in JavaScript/TypeScript:
|
|
1315
|
+
|
|
1316
|
+
```typescript
|
|
1317
|
+
// ⚠️ Dot-notation keys MUST be quoted (JavaScript syntax requirement)
|
|
1318
|
+
await client.getEvents({
|
|
1319
|
+
'context.rootEntityType': 'ORDER', // ✅ Quotes required (has dot)
|
|
1320
|
+
'context.entityRef': 'ORD-123', // ✅ Quotes required (has dot)
|
|
1321
|
+
eventType: 'ORCHESTRATION_AUDIT', // ✅ No quotes needed (simple key)
|
|
1322
|
+
count: 100, // ✅ No quotes needed (simple key)
|
|
1323
|
+
});
|
|
1324
|
+
```
|
|
1313
1325
|
|
|
1314
1326
|
| Parameter | Type | Description | Example |
|
|
1315
1327
|
|-----------|------|-------------|---------|
|
|
@@ -1325,7 +1337,7 @@ All parameters are optional. Context filters use flat dot-notation keys:
|
|
|
1325
1337
|
| `category` | string | Category filter | `'ruleSet'`, `'ACTION'` |
|
|
1326
1338
|
| `from` | string | Start date (UTC ISO 8601) | `'2026-02-01T00:00:00.000Z'` |
|
|
1327
1339
|
| `to` | string | End date (UTC ISO 8601) | `'2026-02-15T23:59:59.999Z'` |
|
|
1328
|
-
| `retailerId` | string/number | Retailer filter (
|
|
1340
|
+
| `retailerId` | string/number | Retailer filter (pass explicitly; no auto-fallback) | `'5'` |
|
|
1329
1341
|
| `start` | number | Pagination offset (default: `0`) | `0` |
|
|
1330
1342
|
| `count` | number | Results per page (default: `100`, max: `5000`) | `1000` |
|
|
1331
1343
|
|
|
@@ -1418,7 +1430,7 @@ const orderEvents = await client.getEvents({
|
|
|
1418
1430
|
| **Time range (to)** | 1 month forward max | API rejects future dates beyond ~1 month |
|
|
1419
1431
|
| **Default time window** | Past 30 days | Applied when `from` is omitted |
|
|
1420
1432
|
| **Date format** | UTC ISO 8601 | `YYYY-MM-DDTHH:mm:ss.SSSZ` |
|
|
1421
|
-
| **retailerId
|
|
1433
|
+
| **retailerId** | Pass explicitly | Unlike `sendEvent()`, no auto-fallback to client config — pass in params if needed |
|
|
1422
1434
|
| **attributes field** | Nullable | Can be `null` (not empty array) — always check before iterating |
|
|
1423
1435
|
| **name field** | Nullable | Can be `null` for some system events |
|
|
1424
1436
|
| **GraphQL** | Not available | Event queries use REST only — no GraphQL `events` root field exists |
|
|
@@ -1658,6 +1670,43 @@ console.log('Status:', response.status);
|
|
|
1658
1670
|
console.log('Data:', response.data);
|
|
1659
1671
|
```
|
|
1660
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
|
+
|
|
1661
1710
|
### What's Handled Automatically
|
|
1662
1711
|
|
|
1663
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>;
|