@fakeware/core 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +2 -2
- package/dist/{config-CSPA4Itj.mjs → config-DGneBZWH.mjs} +17 -14
- package/dist/config-DGneBZWH.mjs.map +1 -0
- package/dist/{index-BZFBgKu8.d.mts → index-Brciwig_.d.mts} +23 -4
- package/dist/{index-Dpb1t7ns.d.mts → index-jYm7NShY.d.mts} +20 -3
- package/dist/index.d.mts +66 -26
- package/dist/index.mjs +249 -34
- package/dist/index.mjs.map +1 -1
- package/dist/shopware/index.d.mts +2 -2
- package/dist/shopware/index.mjs +2 -158
- package/dist/shopware-CIZF8Nuo.mjs +204 -0
- package/dist/shopware-CIZF8Nuo.mjs.map +1 -0
- package/package.json +3 -1
- package/dist/config-CSPA4Itj.mjs.map +0 -1
- package/dist/shopware/index.mjs.map +0 -1
package/dist/config/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as interpolate, c as FakewareConfigFn, d as FakewareUserConfig, f as
|
|
2
|
-
export { type ConfigEnv, ConfigError, DEFAULT_CONFIG_FILENAME, type FakewareConfig, type FakewareConfigFn, type FakewareUserConfig, type LoadConfigOptions, type LoadedConfig, defineConfig, fakewareConfigSchema, interpolate, loadConfig, shopwareSchema };
|
|
1
|
+
import { a as interpolate, c as FakewareConfigFn, d as FakewareUserConfig, f as TransactionConfig, h as transactionSchema, i as loadConfig, l as defineConfig, m as shopwareSchema, n as LoadConfigOptions, o as ConfigError, p as fakewareConfigSchema, r as LoadedConfig, s as ConfigEnv, t as DEFAULT_CONFIG_FILENAME, u as FakewareConfig } from "../index-jYm7NShY.mjs";
|
|
2
|
+
export { type ConfigEnv, ConfigError, DEFAULT_CONFIG_FILENAME, type FakewareConfig, type FakewareConfigFn, type FakewareUserConfig, type LoadConfigOptions, type LoadedConfig, type TransactionConfig, defineConfig, fakewareConfigSchema, interpolate, loadConfig, shopwareSchema, transactionSchema };
|
package/dist/config/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { ConfigError, DEFAULT_CONFIG_FILENAME, defineConfig, fakewareConfigSchema, interpolate, loadConfig, shopwareSchema };
|
|
1
|
+
import { a as transactionSchema, c as defineConfig, i as shopwareSchema, n as loadConfig, o as interpolate, r as fakewareConfigSchema, s as ConfigError, t as DEFAULT_CONFIG_FILENAME } from "../config-DGneBZWH.mjs";
|
|
2
|
+
export { ConfigError, DEFAULT_CONFIG_FILENAME, defineConfig, fakewareConfigSchema, interpolate, loadConfig, shopwareSchema, transactionSchema };
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { createJiti } from "jiti";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/runtime/load-module.ts
|
|
6
|
-
const RUNTIME_TS_HELP = "fakeware needs to import your TypeScript files at runtime. Run it under Bun, or with Node >=22.6 (native type stripping), or via a TypeScript loader such as tsx.";
|
|
7
6
|
var LoadModuleError = class extends Error {};
|
|
8
|
-
|
|
9
|
-
return typeof globalThis.Bun !== "undefined";
|
|
10
|
-
}
|
|
11
|
-
function nodeStripsTypes() {
|
|
12
|
-
const feature = process.features.typescript;
|
|
13
|
-
return feature === "strip" || feature === "transform";
|
|
14
|
-
}
|
|
7
|
+
const jiti = createJiti(import.meta.url);
|
|
15
8
|
async function loadModule(absPath) {
|
|
16
|
-
if (!isBun() && !nodeStripsTypes()) throw new LoadModuleError(RUNTIME_TS_HELP);
|
|
17
9
|
try {
|
|
18
|
-
return await import(
|
|
10
|
+
return await jiti.import(absPath);
|
|
19
11
|
} catch (error) {
|
|
20
12
|
throw new LoadModuleError(`Could not load ${absPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
13
|
}
|
|
@@ -55,7 +47,18 @@ const shopwareSchema = z.object({
|
|
|
55
47
|
clientId: z.string().min(1, "shopware.clientId is required"),
|
|
56
48
|
clientSecret: z.string().min(1, "shopware.clientSecret is required")
|
|
57
49
|
});
|
|
58
|
-
const
|
|
50
|
+
const transactionSchema = z.object({
|
|
51
|
+
onError: z.enum([
|
|
52
|
+
"rollback",
|
|
53
|
+
"continue",
|
|
54
|
+
"stop"
|
|
55
|
+
]).default("rollback"),
|
|
56
|
+
atomic: z.boolean().default(true)
|
|
57
|
+
});
|
|
58
|
+
const fakewareConfigSchema = z.object({
|
|
59
|
+
shopware: shopwareSchema.optional(),
|
|
60
|
+
transaction: transactionSchema.prefault({})
|
|
61
|
+
});
|
|
59
62
|
//#endregion
|
|
60
63
|
//#region src/config/load.ts
|
|
61
64
|
const DEFAULT_CONFIG_FILENAME = "fakeware.config.ts";
|
|
@@ -125,6 +128,6 @@ async function loadConfig(opts = {}) {
|
|
|
125
128
|
};
|
|
126
129
|
}
|
|
127
130
|
//#endregion
|
|
128
|
-
export {
|
|
131
|
+
export { transactionSchema as a, defineConfig as c, shopwareSchema as i, LoadModuleError as l, loadConfig as n, interpolate as o, fakewareConfigSchema as r, ConfigError as s, DEFAULT_CONFIG_FILENAME as t, loadModule as u };
|
|
129
132
|
|
|
130
|
-
//# sourceMappingURL=config-
|
|
133
|
+
//# sourceMappingURL=config-DGneBZWH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-DGneBZWH.mjs","names":[],"sources":["../src/runtime/load-module.ts","../src/config/define.ts","../src/config/errors.ts","../src/config/interpolate.ts","../src/config/schema.ts","../src/config/load.ts"],"sourcesContent":["import { createJiti } from 'jiti'\n\nexport class LoadModuleError extends Error {}\n\nconst jiti = createJiti(import.meta.url)\n\nexport async function loadModule<T = unknown>(absPath: string): Promise<T> {\n try {\n return (await jiti.import(absPath)) as T\n } catch (error) {\n throw new LoadModuleError(\n `Could not load ${absPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n}\n","import type { FakewareUserConfig } from './schema'\n\nexport interface ConfigEnv {\n env: Record<string, string | undefined>\n mode: string\n}\n\nexport type FakewareConfigFn = (env: ConfigEnv) => FakewareUserConfig\n\nexport function defineConfig(config: FakewareUserConfig): FakewareUserConfig\nexport function defineConfig(config: FakewareConfigFn): FakewareConfigFn\nexport function defineConfig(\n config: FakewareUserConfig | FakewareConfigFn,\n): FakewareUserConfig | FakewareConfigFn {\n return config\n}\n","export class ConfigError extends Error {}\n","import { ConfigError } from './errors'\n\nconst ENV_REF = /^\\$([A-Z0-9_]+)$/\n\nexport function interpolate<T>(value: T, env: Record<string, string | undefined>): T {\n if (typeof value === 'string') {\n const match = ENV_REF.exec(value)\n if (!match) return value\n const name = match[1] as string\n const resolved = env[name]\n if (resolved === undefined) {\n throw new ConfigError(`Config references $${name}, but it is not set (check your .env).`)\n }\n return resolved as unknown as T\n }\n if (Array.isArray(value)) {\n return value.map((item) => interpolate(item, env)) as unknown as T\n }\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n out[k] = interpolate(v, env)\n }\n return out as T\n }\n return value\n}\n","import { z } from 'zod'\n\nexport const shopwareSchema = z.object({\n url: z.string().min(1, 'shopware.url is required'),\n clientId: z.string().min(1, 'shopware.clientId is required'),\n clientSecret: z.string().min(1, 'shopware.clientSecret is required'),\n})\n\nexport const transactionSchema = z.object({\n onError: z.enum(['rollback', 'continue', 'stop']).default('rollback'),\n atomic: z.boolean().default(true),\n})\n\nexport const fakewareConfigSchema = z.object({\n shopware: shopwareSchema.optional(),\n transaction: transactionSchema.prefault({}),\n})\n\nexport type TransactionConfig = z.output<typeof transactionSchema>\n\nexport type FakewareConfig = z.output<typeof fakewareConfigSchema>\n\nexport type FakewareUserConfig = z.input<typeof fakewareConfigSchema>\n","import { access, readFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { loadModule } from '../runtime'\nimport type { ShopwareConnection } from '../shopware'\nimport type { ConfigEnv, FakewareConfigFn } from './define'\nimport { ConfigError } from './errors'\nimport { interpolate } from './interpolate'\nimport { type FakewareConfig, type FakewareUserConfig, fakewareConfigSchema } from './schema'\n\nexport const DEFAULT_CONFIG_FILENAME = 'fakeware.config.ts'\n\nexport interface LoadConfigOptions {\n cwd?: string\n configFile?: string\n mode?: string\n}\n\nexport interface LoadedConfig {\n config: FakewareConfig\n connection: ShopwareConnection\n configPath: string\n projectRoot: string\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path)\n return true\n } catch {\n return false\n }\n}\n\nasync function findConfig(cwd: string): Promise<string> {\n let dir = cwd\n for (;;) {\n const candidate = join(dir, DEFAULT_CONFIG_FILENAME)\n if (await fileExists(candidate)) return candidate\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n throw new ConfigError(\n `No ${DEFAULT_CONFIG_FILENAME} found in ${cwd} or any parent directory. Run \\`fakeware init\\` first.`,\n )\n}\n\nasync function readEnvFile(projectRoot: string): Promise<Record<string, string>> {\n const path = join(projectRoot, '.env')\n if (!(await fileExists(path))) return {}\n const out: Record<string, string> = {}\n const contents = await readFile(path, 'utf8')\n for (const raw of contents.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n const eq = line.indexOf('=')\n if (eq === -1) continue\n const key = line.slice(0, eq).trim()\n let val = line.slice(eq + 1).trim()\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1)\n }\n out[key] = val\n }\n return out\n}\n\nfunction isConfigFn(value: unknown): value is FakewareConfigFn {\n return typeof value === 'function'\n}\n\nexport async function loadConfig(opts: LoadConfigOptions = {}): Promise<LoadedConfig> {\n const cwd = opts.cwd ?? process.cwd()\n const configPath = opts.configFile\n ? isAbsolute(opts.configFile)\n ? opts.configFile\n : resolve(cwd, opts.configFile)\n : await findConfig(cwd)\n const projectRoot = dirname(configPath)\n\n const env: Record<string, string | undefined> = {\n ...process.env,\n ...(await readEnvFile(projectRoot)),\n }\n\n const mod = await loadModule<{ default?: unknown }>(configPath)\n const exported = mod.default\n if (exported === undefined) {\n throw new ConfigError(`${configPath} must \\`export default defineConfig(...)\\`.`)\n }\n\n const configEnv: ConfigEnv = { env, mode: opts.mode ?? 'development' }\n const raw = isConfigFn(exported) ? exported(configEnv) : (exported as FakewareUserConfig)\n\n const interpolated = interpolate(raw, env)\n\n const parsed = fakewareConfigSchema.safeParse(interpolated)\n if (!parsed.success) {\n throw new ConfigError(`Invalid config in ${configPath}: ${parsed.error.message}`)\n }\n\n const { shopware } = parsed.data\n if (!shopware) {\n throw new ConfigError(\n `No \\`shopware\\` connection configured in ${configPath}. up/down need a shop to talk to.`,\n )\n }\n\n return { config: parsed.data, connection: shopware, configPath, projectRoot }\n}\n"],"mappings":"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"}
|
|
@@ -28,16 +28,35 @@ declare function fetchShopInfo(connection: ShopwareConnection): Promise<ShopInfo
|
|
|
28
28
|
type SinkRecord = Record<string, unknown> & {
|
|
29
29
|
id: string;
|
|
30
30
|
};
|
|
31
|
+
type SyncOperation = {
|
|
32
|
+
entity: string;
|
|
33
|
+
action: 'upsert';
|
|
34
|
+
records: SinkRecord[];
|
|
35
|
+
} | {
|
|
36
|
+
entity: string;
|
|
37
|
+
action: 'delete';
|
|
38
|
+
ids: string[];
|
|
39
|
+
};
|
|
40
|
+
interface BatchProgress {
|
|
41
|
+
records: number;
|
|
42
|
+
recordsTotal: number;
|
|
43
|
+
batches: number;
|
|
44
|
+
batchesTotal: number;
|
|
45
|
+
}
|
|
46
|
+
type OnBatch = (progress: BatchProgress) => void;
|
|
31
47
|
interface ShopwareSink {
|
|
32
|
-
upsert(entity: string, records: SinkRecord[]): Promise<void>;
|
|
33
|
-
delete(entity: string, ids: string[]): Promise<void>;
|
|
48
|
+
upsert(entity: string, records: SinkRecord[], onBatch?: OnBatch): Promise<void>;
|
|
49
|
+
delete(entity: string, ids: string[], onBatch?: OnBatch): Promise<void>;
|
|
50
|
+
applyAtomic(operations: SyncOperation[]): Promise<void>;
|
|
34
51
|
}
|
|
35
52
|
//#endregion
|
|
36
53
|
//#region src/shopware/sink.d.ts
|
|
37
54
|
interface SyncSinkOptions {
|
|
38
55
|
client?: ShopwareClient;
|
|
39
56
|
}
|
|
57
|
+
declare const ATOMIC_REQUEST_BYTE_LIMIT: number;
|
|
58
|
+
declare function estimateSyncBytes(operations: SyncOperation[]): number;
|
|
40
59
|
declare function createSyncSink(connection: ShopwareConnection, options?: SyncSinkOptions): ShopwareSink;
|
|
41
60
|
//#endregion
|
|
42
|
-
export {
|
|
43
|
-
//# sourceMappingURL=index-
|
|
61
|
+
export { ShopwareSink as a, fetchShopInfo as c, ShopwareConnectionError as d, ShopwareClient as f, ShopwareConnection as h, BatchProgress as i, toConnectionError as l, ShopInfo as m, createSyncSink as n, SinkRecord as o, createShopwareClient as p, estimateSyncBytes as r, SyncOperation as s, ATOMIC_REQUEST_BYTE_LIMIT as t, validateConnection as u };
|
|
62
|
+
//# sourceMappingURL=index-Brciwig_.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as ShopwareConnection } from "./index-Brciwig_.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/config/schema.d.ts
|
|
@@ -7,13 +7,30 @@ declare const shopwareSchema: z.ZodObject<{
|
|
|
7
7
|
clientId: z.ZodString;
|
|
8
8
|
clientSecret: z.ZodString;
|
|
9
9
|
}, z.core.$strip>;
|
|
10
|
+
declare const transactionSchema: z.ZodObject<{
|
|
11
|
+
onError: z.ZodDefault<z.ZodEnum<{
|
|
12
|
+
rollback: "rollback";
|
|
13
|
+
continue: "continue";
|
|
14
|
+
stop: "stop";
|
|
15
|
+
}>>;
|
|
16
|
+
atomic: z.ZodDefault<z.ZodBoolean>;
|
|
17
|
+
}, z.core.$strip>;
|
|
10
18
|
declare const fakewareConfigSchema: z.ZodObject<{
|
|
11
19
|
shopware: z.ZodOptional<z.ZodObject<{
|
|
12
20
|
url: z.ZodString;
|
|
13
21
|
clientId: z.ZodString;
|
|
14
22
|
clientSecret: z.ZodString;
|
|
15
23
|
}, z.core.$strip>>;
|
|
24
|
+
transaction: z.ZodPrefault<z.ZodObject<{
|
|
25
|
+
onError: z.ZodDefault<z.ZodEnum<{
|
|
26
|
+
rollback: "rollback";
|
|
27
|
+
continue: "continue";
|
|
28
|
+
stop: "stop";
|
|
29
|
+
}>>;
|
|
30
|
+
atomic: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
16
32
|
}, z.core.$strip>;
|
|
33
|
+
type TransactionConfig = z.output<typeof transactionSchema>;
|
|
17
34
|
type FakewareConfig = z.output<typeof fakewareConfigSchema>;
|
|
18
35
|
type FakewareUserConfig = z.input<typeof fakewareConfigSchema>;
|
|
19
36
|
//#endregion
|
|
@@ -47,5 +64,5 @@ interface LoadedConfig {
|
|
|
47
64
|
}
|
|
48
65
|
declare function loadConfig(opts?: LoadConfigOptions): Promise<LoadedConfig>;
|
|
49
66
|
//#endregion
|
|
50
|
-
export { interpolate as a, FakewareConfigFn as c, FakewareUserConfig as d,
|
|
51
|
-
//# sourceMappingURL=index-
|
|
67
|
+
export { interpolate as a, FakewareConfigFn as c, FakewareUserConfig as d, TransactionConfig as f, transactionSchema as h, loadConfig as i, defineConfig as l, shopwareSchema as m, LoadConfigOptions as n, ConfigError as o, fakewareConfigSchema as p, LoadedConfig as r, ConfigEnv as s, DEFAULT_CONFIG_FILENAME as t, FakewareConfig as u };
|
|
68
|
+
//# sourceMappingURL=index-jYm7NShY.d.mts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as LoadedConfig } from "./index-
|
|
2
|
-
import {
|
|
1
|
+
import { r as LoadedConfig } from "./index-jYm7NShY.mjs";
|
|
2
|
+
import { a as ShopwareSink, i as BatchProgress, o as SinkRecord, s as SyncOperation } from "./index-Brciwig_.mjs";
|
|
3
3
|
import { Schemas } from "@shopware/api-client/admin-api-types";
|
|
4
4
|
|
|
5
5
|
//#region src/define/ctx.d.ts
|
|
@@ -44,32 +44,28 @@ declare function refs(entity: string): string[];
|
|
|
44
44
|
//#region src/define/errors.d.ts
|
|
45
45
|
declare class RefError extends Error {}
|
|
46
46
|
//#endregion
|
|
47
|
-
//#region src/engine/errors.d.ts
|
|
48
|
-
declare class GraphError extends Error {}
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region src/engine/manifest.d.ts
|
|
51
|
-
interface ManifestRecord {
|
|
52
|
-
id: string;
|
|
53
|
-
hash: string;
|
|
54
|
-
}
|
|
55
|
-
interface ManifestEntity {
|
|
56
|
-
entity: string;
|
|
57
|
-
records: ManifestRecord[];
|
|
58
|
-
}
|
|
59
|
-
interface Manifest {
|
|
60
|
-
version: 1;
|
|
61
|
-
fakewareVersion: string;
|
|
62
|
-
createdAt: string;
|
|
63
|
-
shopwareUrl: string;
|
|
64
|
-
entities: ManifestEntity[];
|
|
65
|
-
checksum: string;
|
|
66
|
-
}
|
|
67
|
-
declare function readManifest(projectRoot: string, shopwareUrl: string): Promise<Manifest | null>;
|
|
68
|
-
//#endregion
|
|
69
47
|
//#region src/engine/run.d.ts
|
|
70
48
|
interface Reporter {
|
|
71
|
-
onStart?(entity: string): void;
|
|
49
|
+
onStart?(entity: string, records?: number): void;
|
|
50
|
+
onBatch?(progress: BatchProgress): void;
|
|
72
51
|
onStep?(step: ReportStep): void;
|
|
52
|
+
onTransactionStart?(info: {
|
|
53
|
+
mode: 'atomic' | 'saga';
|
|
54
|
+
}): void;
|
|
55
|
+
onCommit?(info: {
|
|
56
|
+
committed: number;
|
|
57
|
+
}): void;
|
|
58
|
+
onCompensate?(entity: string, count: number): void;
|
|
59
|
+
onCompensateFail?(entity: string): void;
|
|
60
|
+
onSkip?(info: {
|
|
61
|
+
entity: string;
|
|
62
|
+
error: unknown;
|
|
63
|
+
}): void;
|
|
64
|
+
onStop?(info: {
|
|
65
|
+
failedEntity: string;
|
|
66
|
+
error: unknown;
|
|
67
|
+
message: string;
|
|
68
|
+
}): void;
|
|
73
69
|
}
|
|
74
70
|
interface ReportStep {
|
|
75
71
|
entity: string;
|
|
@@ -78,6 +74,11 @@ interface ReportStep {
|
|
|
78
74
|
unchanged: number;
|
|
79
75
|
deleted: number;
|
|
80
76
|
}
|
|
77
|
+
type OnError = 'rollback' | 'continue' | 'stop';
|
|
78
|
+
interface TransactionOptions {
|
|
79
|
+
onError: OnError;
|
|
80
|
+
atomic: boolean;
|
|
81
|
+
}
|
|
81
82
|
interface RunOptions {
|
|
82
83
|
loaded: LoadedConfig;
|
|
83
84
|
sink: ShopwareSink;
|
|
@@ -85,10 +86,14 @@ interface RunOptions {
|
|
|
85
86
|
reporter?: Reporter;
|
|
86
87
|
fakewareVersion?: string;
|
|
87
88
|
now?: string;
|
|
89
|
+
transaction?: TransactionOptions;
|
|
88
90
|
}
|
|
89
91
|
interface UpResult {
|
|
90
92
|
steps: ReportStep[];
|
|
91
93
|
manifestWritten: boolean;
|
|
94
|
+
mode: 'atomic' | 'saga' | 'dry-run' | 'noop';
|
|
95
|
+
committed: number;
|
|
96
|
+
rolledBack: number;
|
|
92
97
|
}
|
|
93
98
|
interface DownResult {
|
|
94
99
|
steps: ReportStep[];
|
|
@@ -97,8 +102,43 @@ interface DownResult {
|
|
|
97
102
|
declare function runUp(opts: RunOptions): Promise<UpResult>;
|
|
98
103
|
declare function runDown(opts: RunOptions): Promise<DownResult>;
|
|
99
104
|
//#endregion
|
|
105
|
+
//#region src/engine/errors.d.ts
|
|
106
|
+
declare class GraphError extends Error {}
|
|
107
|
+
declare class TransactionError extends Error {
|
|
108
|
+
readonly rolledBack: ReportStep[];
|
|
109
|
+
readonly failedEntity: string;
|
|
110
|
+
readonly unrevertableUpdates: boolean;
|
|
111
|
+
readonly compensationErrors: unknown[];
|
|
112
|
+
constructor(message: string, options: {
|
|
113
|
+
cause: unknown;
|
|
114
|
+
rolledBack: ReportStep[];
|
|
115
|
+
failedEntity: string;
|
|
116
|
+
unrevertableUpdates?: boolean;
|
|
117
|
+
compensationErrors?: unknown[];
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/engine/manifest.d.ts
|
|
122
|
+
interface ManifestRecord {
|
|
123
|
+
id: string;
|
|
124
|
+
hash: string;
|
|
125
|
+
}
|
|
126
|
+
interface ManifestEntity {
|
|
127
|
+
entity: string;
|
|
128
|
+
records: ManifestRecord[];
|
|
129
|
+
}
|
|
130
|
+
interface Manifest {
|
|
131
|
+
version: 1;
|
|
132
|
+
fakewareVersion: string;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
shopwareUrl: string;
|
|
135
|
+
entities: ManifestEntity[];
|
|
136
|
+
checksum: string;
|
|
137
|
+
}
|
|
138
|
+
declare function readManifest(projectRoot: string, shopwareUrl: string): Promise<Manifest | null>;
|
|
139
|
+
//#endregion
|
|
100
140
|
//#region src/runtime/load-module.d.ts
|
|
101
141
|
declare class LoadModuleError extends Error {}
|
|
102
142
|
//#endregion
|
|
103
|
-
export { type Ctx, type DefineRecord, type DownResult, type EntityName, GraphError, LoadModuleError, type Manifest, type ManifestEntity, type ManifestRecord, RefError, type ReportStep, type Reporter, type RunOptions, type ShopwareSink, type SinkRecord, type UpResult, define, many, readManifest, ref, refs, runDown, runUp };
|
|
143
|
+
export { type BatchProgress, type Ctx, type DefineRecord, type DownResult, type EntityName, GraphError, LoadModuleError, type Manifest, type ManifestEntity, type ManifestRecord, type OnError, RefError, type ReportStep, type Reporter, type RunOptions, type ShopwareSink, type SinkRecord, type SyncOperation, TransactionError, type TransactionOptions, type UpResult, define, many, readManifest, ref, refs, runDown, runUp };
|
|
104
144
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as LoadModuleError, s as ConfigError, u as loadModule } from "./config-DGneBZWH.mjs";
|
|
2
|
+
import { r as estimateSyncBytes } from "./shopware-CIZF8Nuo.mjs";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
@@ -149,6 +150,20 @@ function resolveValue(value, ctx) {
|
|
|
149
150
|
//#endregion
|
|
150
151
|
//#region src/engine/errors.ts
|
|
151
152
|
var GraphError = class extends Error {};
|
|
153
|
+
var TransactionError = class extends Error {
|
|
154
|
+
rolledBack;
|
|
155
|
+
failedEntity;
|
|
156
|
+
unrevertableUpdates;
|
|
157
|
+
compensationErrors;
|
|
158
|
+
constructor(message, options) {
|
|
159
|
+
super(message, { cause: options.cause });
|
|
160
|
+
this.name = "TransactionError";
|
|
161
|
+
this.rolledBack = options.rolledBack;
|
|
162
|
+
this.failedEntity = options.failedEntity;
|
|
163
|
+
this.unrevertableUpdates = options.unrevertableUpdates ?? false;
|
|
164
|
+
this.compensationErrors = options.compensationErrors ?? [];
|
|
165
|
+
}
|
|
166
|
+
};
|
|
152
167
|
//#endregion
|
|
153
168
|
//#region src/engine/build-graph.ts
|
|
154
169
|
function ownerByIdOf(refIndex) {
|
|
@@ -309,58 +324,257 @@ function priorHashes(manifest) {
|
|
|
309
324
|
for (const e of manifest?.entities ?? []) map.set(e.entity, new Map(e.records.map((r) => [r.id, r.hash])));
|
|
310
325
|
return map;
|
|
311
326
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const manifestRecords = records.map((record) => {
|
|
327
|
+
function resolveTransaction(opts) {
|
|
328
|
+
return opts.transaction ?? opts.loaded.config.transaction;
|
|
329
|
+
}
|
|
330
|
+
function diffEntity(entity, records, prior) {
|
|
331
|
+
const toWrite = [];
|
|
332
|
+
const createdIds = [];
|
|
333
|
+
let created = 0;
|
|
334
|
+
let updated = 0;
|
|
335
|
+
let unchanged = 0;
|
|
336
|
+
return {
|
|
337
|
+
entity,
|
|
338
|
+
toWrite,
|
|
339
|
+
createdIds,
|
|
340
|
+
manifestRecords: records.map((record) => {
|
|
327
341
|
const hash = recordHash(record);
|
|
328
|
-
const previous =
|
|
329
|
-
if (previous === void 0)
|
|
330
|
-
|
|
342
|
+
const previous = prior.get(record.id);
|
|
343
|
+
if (previous === void 0) {
|
|
344
|
+
created++;
|
|
345
|
+
createdIds.push(record.id);
|
|
346
|
+
} else if (previous === hash) unchanged++;
|
|
331
347
|
else updated++;
|
|
332
348
|
if (previous !== hash) toWrite.push(record);
|
|
333
349
|
return {
|
|
334
350
|
id: record.id,
|
|
335
351
|
hash
|
|
336
352
|
};
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
manifestEntities.push({
|
|
340
|
-
entity,
|
|
341
|
-
records: manifestRecords
|
|
342
|
-
});
|
|
343
|
-
const step = {
|
|
353
|
+
}),
|
|
354
|
+
step: {
|
|
344
355
|
entity,
|
|
345
356
|
created,
|
|
346
357
|
updated,
|
|
347
358
|
unchanged,
|
|
348
359
|
deleted: 0
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function partialWrite(w, committed) {
|
|
364
|
+
if (committed <= 0) return null;
|
|
365
|
+
const createdSet = new Set(w.createdIds);
|
|
366
|
+
const prefix = w.toWrite.slice(0, committed);
|
|
367
|
+
const createdIds = prefix.map((r) => r.id).filter((id) => createdSet.has(id));
|
|
368
|
+
const updated = prefix.length - createdIds.length;
|
|
369
|
+
if (createdIds.length === 0 && updated === 0) return null;
|
|
370
|
+
return {
|
|
371
|
+
entity: w.entity,
|
|
372
|
+
toWrite: [],
|
|
373
|
+
createdIds,
|
|
374
|
+
manifestRecords: [],
|
|
375
|
+
step: {
|
|
376
|
+
entity: w.entity,
|
|
377
|
+
created: 0,
|
|
378
|
+
updated,
|
|
379
|
+
unchanged: 0,
|
|
380
|
+
deleted: 0
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
async function runUp(opts) {
|
|
385
|
+
const { loaded, sink, dryRun, reporter } = opts;
|
|
386
|
+
const tx = resolveTransaction(opts);
|
|
387
|
+
const plan = buildWritePlan(await evaluateDataFiles(await discoverDataFiles(loaded.projectRoot)));
|
|
388
|
+
const prior = priorHashes(await readManifest(loaded.projectRoot, loaded.connection.url));
|
|
389
|
+
const writes = plan.order.map((entity) => diffEntity(entity, plan.records.get(entity) ?? [], prior.get(entity) ?? /* @__PURE__ */ new Map()));
|
|
390
|
+
const steps = writes.map((w) => w.step);
|
|
391
|
+
const manifestEntities = writes.map((w) => ({
|
|
392
|
+
entity: w.entity,
|
|
393
|
+
records: w.manifestRecords
|
|
394
|
+
}));
|
|
395
|
+
const committed = steps.reduce((n, s) => n + s.created + s.updated, 0);
|
|
396
|
+
const pending = writes.filter((w) => w.toWrite.length > 0);
|
|
397
|
+
if (dryRun || pending.length === 0) {
|
|
398
|
+
for (const w of writes) {
|
|
399
|
+
reporter?.onStart?.(w.entity);
|
|
400
|
+
reporter?.onStep?.(w.step);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
steps,
|
|
404
|
+
manifestWritten: false,
|
|
405
|
+
mode: dryRun ? "dry-run" : "noop",
|
|
406
|
+
committed: 0,
|
|
407
|
+
rolledBack: 0
|
|
349
408
|
};
|
|
350
|
-
steps.push(step);
|
|
351
|
-
reporter?.onStep?.(step);
|
|
352
409
|
}
|
|
353
|
-
|
|
410
|
+
const writeManifestNow = (entities) => writeManifest(loaded.projectRoot, buildManifest({
|
|
354
411
|
fakewareVersion: opts.fakewareVersion ?? "0.0.0",
|
|
355
412
|
createdAt: opts.now ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
356
413
|
shopwareUrl: loaded.connection.url,
|
|
357
|
-
entities
|
|
414
|
+
entities
|
|
415
|
+
}));
|
|
416
|
+
const operations = pending.map((w) => ({
|
|
417
|
+
entity: w.entity,
|
|
418
|
+
action: "upsert",
|
|
419
|
+
records: w.toWrite
|
|
358
420
|
}));
|
|
421
|
+
if (tx.atomic && estimateSyncBytes(operations) <= 5242880) {
|
|
422
|
+
reporter?.onTransactionStart?.({ mode: "atomic" });
|
|
423
|
+
try {
|
|
424
|
+
await sink.applyAtomic(operations);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
const message = "Apply failed — Shopware rolled back all changes.";
|
|
427
|
+
reporter?.onStop?.({
|
|
428
|
+
failedEntity: "",
|
|
429
|
+
error,
|
|
430
|
+
message
|
|
431
|
+
});
|
|
432
|
+
throw new TransactionError(message, {
|
|
433
|
+
cause: error,
|
|
434
|
+
rolledBack: [],
|
|
435
|
+
failedEntity: ""
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
await writeManifestNow(manifestEntities);
|
|
439
|
+
reporter?.onCommit?.({ committed });
|
|
440
|
+
return {
|
|
441
|
+
steps,
|
|
442
|
+
manifestWritten: true,
|
|
443
|
+
mode: "atomic",
|
|
444
|
+
committed,
|
|
445
|
+
rolledBack: 0
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
reporter?.onTransactionStart?.({ mode: "saga" });
|
|
449
|
+
const written = [];
|
|
450
|
+
const skipped = [];
|
|
451
|
+
for (const w of pending) {
|
|
452
|
+
reporter?.onStart?.(w.entity, w.toWrite.length);
|
|
453
|
+
let committedRecords = 0;
|
|
454
|
+
try {
|
|
455
|
+
await sink.upsert(w.entity, w.toWrite, (progress) => {
|
|
456
|
+
committedRecords = progress.records;
|
|
457
|
+
reporter?.onBatch?.(progress);
|
|
458
|
+
});
|
|
459
|
+
written.push(w);
|
|
460
|
+
reporter?.onStep?.(w.step);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (tx.onError === "stop") {
|
|
463
|
+
const message = `Writing ${w.entity} failed — stopped.`;
|
|
464
|
+
reporter?.onStop?.({
|
|
465
|
+
failedEntity: w.entity,
|
|
466
|
+
error,
|
|
467
|
+
message
|
|
468
|
+
});
|
|
469
|
+
throw new TransactionError(message, {
|
|
470
|
+
cause: error,
|
|
471
|
+
rolledBack: [],
|
|
472
|
+
failedEntity: w.entity
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (tx.onError === "continue") {
|
|
476
|
+
skipped.push({
|
|
477
|
+
entity: w.entity,
|
|
478
|
+
error
|
|
479
|
+
});
|
|
480
|
+
reporter?.onSkip?.({
|
|
481
|
+
entity: w.entity,
|
|
482
|
+
error
|
|
483
|
+
});
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const message = `Could not apply ${w.entity}.`;
|
|
487
|
+
reporter?.onStop?.({
|
|
488
|
+
failedEntity: w.entity,
|
|
489
|
+
error,
|
|
490
|
+
message
|
|
491
|
+
});
|
|
492
|
+
const partial = partialWrite(w, committedRecords);
|
|
493
|
+
const { rolledBack, unrevertableUpdates, compensationErrors } = await compensate(sink, partial ? [...written, partial] : written, reporter);
|
|
494
|
+
throw new TransactionError(message, {
|
|
495
|
+
cause: error,
|
|
496
|
+
rolledBack,
|
|
497
|
+
failedEntity: w.entity,
|
|
498
|
+
unrevertableUpdates,
|
|
499
|
+
compensationErrors
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
for (const w of writes) if (!written.includes(w) && !skipped.some((s) => s.entity === w.entity)) {
|
|
504
|
+
reporter?.onStart?.(w.entity);
|
|
505
|
+
reporter?.onStep?.(w.step);
|
|
506
|
+
}
|
|
507
|
+
if (skipped.length > 0) {
|
|
508
|
+
await writeManifestNow(restrictManifest(manifestEntities, prior, new Set(written.map((w) => w.entity))));
|
|
509
|
+
const first = skipped[0];
|
|
510
|
+
throw new TransactionError(`Applied with ${skipped.length} skipped entit${skipped.length === 1 ? "y" : "ies"}.`, {
|
|
511
|
+
cause: skipped,
|
|
512
|
+
rolledBack: [],
|
|
513
|
+
failedEntity: first.entity
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
await writeManifestNow(manifestEntities);
|
|
517
|
+
reporter?.onCommit?.({ committed });
|
|
359
518
|
return {
|
|
360
519
|
steps,
|
|
361
|
-
manifestWritten:
|
|
520
|
+
manifestWritten: true,
|
|
521
|
+
mode: "saga",
|
|
522
|
+
committed,
|
|
523
|
+
rolledBack: 0
|
|
362
524
|
};
|
|
363
525
|
}
|
|
526
|
+
async function compensate(sink, written, reporter) {
|
|
527
|
+
const rolledBack = [];
|
|
528
|
+
const compensationErrors = [];
|
|
529
|
+
for (const w of [...written].reverse()) {
|
|
530
|
+
if (w.createdIds.length === 0) continue;
|
|
531
|
+
try {
|
|
532
|
+
await sink.delete(w.entity, w.createdIds);
|
|
533
|
+
rolledBack.push({
|
|
534
|
+
entity: w.entity,
|
|
535
|
+
created: 0,
|
|
536
|
+
updated: 0,
|
|
537
|
+
unchanged: 0,
|
|
538
|
+
deleted: w.createdIds.length
|
|
539
|
+
});
|
|
540
|
+
reporter?.onCompensate?.(w.entity, w.createdIds.length);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
compensationErrors.push(error);
|
|
543
|
+
reporter?.onCompensateFail?.(w.entity);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
rolledBack,
|
|
548
|
+
unrevertableUpdates: written.some((w) => w.step.updated > 0),
|
|
549
|
+
compensationErrors
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function restrictManifest(desired, prior, writtenEntities) {
|
|
553
|
+
const out = [];
|
|
554
|
+
const seen = /* @__PURE__ */ new Set();
|
|
555
|
+
for (const e of desired) {
|
|
556
|
+
seen.add(e.entity);
|
|
557
|
+
if (writtenEntities.has(e.entity)) out.push(e);
|
|
558
|
+
else {
|
|
559
|
+
const priorRecords = prior.get(e.entity);
|
|
560
|
+
if (priorRecords && priorRecords.size > 0) out.push({
|
|
561
|
+
entity: e.entity,
|
|
562
|
+
records: [...priorRecords].map(([id, hash]) => ({
|
|
563
|
+
id,
|
|
564
|
+
hash
|
|
565
|
+
}))
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
for (const [entity, records] of prior) if (!seen.has(entity) && records.size > 0) out.push({
|
|
570
|
+
entity,
|
|
571
|
+
records: [...records].map(([id, hash]) => ({
|
|
572
|
+
id,
|
|
573
|
+
hash
|
|
574
|
+
}))
|
|
575
|
+
});
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
364
578
|
async function runDown(opts) {
|
|
365
579
|
const { loaded, sink, dryRun, reporter } = opts;
|
|
366
580
|
const manifest = await readManifest(loaded.projectRoot, loaded.connection.url);
|
|
@@ -368,11 +582,12 @@ async function runDown(opts) {
|
|
|
368
582
|
steps: [],
|
|
369
583
|
reverted: false
|
|
370
584
|
};
|
|
585
|
+
reporter?.onTransactionStart?.({ mode: "saga" });
|
|
371
586
|
const steps = [];
|
|
372
587
|
for (const entity of [...manifest.entities].reverse()) {
|
|
373
|
-
reporter?.onStart?.(entity.entity);
|
|
374
588
|
const ids = entity.records.map((r) => r.id);
|
|
375
|
-
|
|
589
|
+
reporter?.onStart?.(entity.entity, ids.length);
|
|
590
|
+
if (!dryRun && ids.length > 0) await sink.delete(entity.entity, ids, (progress) => reporter?.onBatch?.(progress));
|
|
376
591
|
const step = {
|
|
377
592
|
entity: entity.entity,
|
|
378
593
|
created: 0,
|
|
@@ -390,6 +605,6 @@ async function runDown(opts) {
|
|
|
390
605
|
};
|
|
391
606
|
}
|
|
392
607
|
//#endregion
|
|
393
|
-
export { GraphError, LoadModuleError, RefError, define, many, readManifest, ref, refs, runDown, runUp };
|
|
608
|
+
export { GraphError, LoadModuleError, RefError, TransactionError, define, many, readManifest, ref, refs, runDown, runUp };
|
|
394
609
|
|
|
395
610
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["refById","refsByEntity"],"sources":["../src/define/errors.ts","../src/define/is-plain-object.ts","../src/define/ids.ts","../src/define/registry.ts","../src/define/define.ts","../src/define/resolve.ts","../src/engine/errors.ts","../src/engine/build-graph.ts","../src/engine/discover.ts","../src/engine/evaluate.ts","../src/engine/manifest.ts","../src/engine/run.ts"],"sourcesContent":["export class RefError extends Error {}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)\n )\n}\n","import { createHash } from 'node:crypto'\nimport { isPlainObject } from './is-plain-object'\n\nconst FAKEWARE_NAMESPACE = 'b1e0a3f4-6c2d-5a8b-9e7f-0d1c2b3a4e5f'\n\nfunction uuidBytes(uuid: string): Uint8Array {\n const hex = uuid.replace(/-/g, '')\n const bytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return bytes\n}\n\nconst NAMESPACE_BYTES = uuidBytes(FAKEWARE_NAMESPACE)\n\nfunction uuidv5(name: string): string {\n const hash = createHash('sha1')\n hash.update(NAMESPACE_BYTES)\n hash.update(name, 'utf8')\n const digest = hash.digest()\n\n const bytes = digest.subarray(0, 16)\n bytes[6] = ((bytes[6] as number) & 0x0f) | 0x50\n bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80\n\n return bytes.toString('hex')\n}\n\nexport function deterministicId(entity: string, key: string): string {\n return uuidv5(`${entity}:${key}`)\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize)\n if (isPlainObject(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value).sort()) {\n sorted[k] = canonicalize(value[k])\n }\n return sorted\n }\n return value\n}\n\nexport function recordHash(payload: unknown): string {\n return createHash('sha256')\n .update(JSON.stringify(canonicalize(payload)))\n .digest('hex')\n}\n","import type { Ctx } from './ctx'\nimport { deterministicId } from './ids'\n\ntype RecordObject = Record<string, unknown>\nexport type RecordValue = RecordObject | ((ctx: Ctx) => RecordObject)\n\ninterface RawEntry {\n entity: string\n key?: string\n value: RecordValue\n}\n\nexport type DrainedEntries = { entity: string; entries: RawEntry[] }[]\n\nexport interface RefIndex {\n byEntity: Map<string, { byKey: Map<string, string>; all: string[] }>\n}\n\nlet entries: RawEntry[] = []\n\nexport function resetRegistry(): void {\n entries = []\n}\n\nfunction staticKey(value: RecordValue): string | undefined {\n if (typeof value !== 'function') {\n const k = (value as RecordObject).$key\n if (typeof k === 'string') return k\n }\n return undefined\n}\n\nexport function defineRecords(entity: string, recordOrRecords: RecordValue | RecordValue[]): void {\n const list = Array.isArray(recordOrRecords) ? recordOrRecords : [recordOrRecords]\n for (const value of list) {\n entries.push({ entity, key: staticKey(value), value })\n }\n}\n\nexport function drain(): DrainedEntries {\n const order: string[] = []\n const byEntity = new Map<string, RawEntry[]>()\n for (const e of entries) {\n let bucket = byEntity.get(e.entity)\n if (!bucket) {\n bucket = []\n byEntity.set(e.entity, bucket)\n order.push(e.entity)\n }\n bucket.push(e)\n }\n return order.map((entity) => ({ entity, entries: byEntity.get(entity) as RawEntry[] }))\n}\n\nexport function buildRefIndex(drained: DrainedEntries): {\n refIndex: RefIndex\n ids: Map<RawEntry, string>\n} {\n const refIndex: RefIndex = { byEntity: new Map() }\n const ids = new Map<RawEntry, string>()\n\n for (const { entity, entries: bucket } of drained) {\n const slot = { byKey: new Map<string, string>(), all: [] as string[] }\n refIndex.byEntity.set(entity, slot)\n bucket.forEach((entry, i) => {\n const idKey = entry.key ?? String(i)\n const id = deterministicId(entity, idKey)\n ids.set(entry, id)\n slot.all.push(id)\n if (entry.key) slot.byKey.set(entry.key, id)\n })\n }\n\n return { refIndex, ids }\n}\n\nexport type { RawEntry }\n","import type { Ctx } from './ctx'\nimport { RefError } from './errors'\nimport { defineRecords, type RecordValue, type RefIndex } from './registry'\nimport type { DefineRecord, EntityName } from './schema'\n\nexport function define<const E extends EntityName>(\n entity: E,\n records: DefineRecord<E> | readonly DefineRecord<E>[],\n): void {\n defineRecords(entity, records as RecordValue | RecordValue[])\n}\n\nexport function many<R extends Record<string, unknown>>(n: number, fn: (ctx: Ctx) => R): R {\n return Array.from({ length: n }, () => fn) as unknown as R\n}\n\nlet active: RefIndex | undefined\n\nexport function setActiveRefIndex(refIndex: RefIndex | undefined): void {\n active = refIndex\n}\n\nfunction requireActive(): RefIndex {\n if (!active) {\n throw new RefError('ref()/refs() may only be called while resolving definitions.')\n }\n return active\n}\n\nexport function ref(path: string): string {\n const slash = path.indexOf('/')\n if (slash === -1) {\n throw new RefError(`ref('${path}') must be of the form 'entity/key'.`)\n }\n const entity = path.slice(0, slash)\n const key = path.slice(slash + 1)\n const id = requireActive().byEntity.get(entity)?.byKey.get(key)\n if (!id) {\n throw new RefError(`ref('${path}') does not match any defined record.`)\n }\n return id\n}\n\nexport function refs(entity: string): string[] {\n const slot = requireActive().byEntity.get(entity)\n if (!slot) {\n throw new RefError(`refs('${entity}') does not match any defined entity.`)\n }\n return [...slot.all]\n}\n","import type { Ctx } from './ctx'\nimport { isPlainObject } from './is-plain-object'\n\nexport function resolveValue(value: unknown, ctx: Ctx): unknown {\n if (typeof value === 'function') {\n return resolveValue((value as (ctx: Ctx) => unknown)(ctx), ctx)\n }\n if (Array.isArray(value)) {\n return value.map((item) => resolveValue(item, ctx))\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {}\n for (const [key, v] of Object.entries(value)) {\n if (key === '$key') continue\n out[key] = resolveValue(v, ctx)\n }\n return out\n }\n return value\n}\n","export class GraphError extends Error {}\n","import {\n buildRefIndex,\n type Ctx,\n type DrainedEntries,\n isPlainObject,\n type RefIndex,\n ref as refById,\n refs as refsByEntity,\n resolveValue,\n setActiveRefIndex,\n} from '../define'\nimport type { SinkRecord } from '../domain'\nimport { GraphError } from './errors'\n\nexport interface WritePlan {\n order: string[]\n records: Map<string, SinkRecord[]>\n}\n\nfunction ownerByIdOf(refIndex: RefIndex): Map<string, string> {\n const owner = new Map<string, string>()\n for (const [entity, slot] of refIndex.byEntity) {\n for (const id of slot.all) owner.set(id, entity)\n }\n return owner\n}\n\nfunction collectIdRefs(value: unknown, ownerById: Map<string, string>, into: Set<string>): void {\n if (typeof value === 'string') {\n const owner = ownerById.get(value)\n if (owner) into.add(owner)\n return\n }\n if (Array.isArray(value)) {\n for (const item of value) collectIdRefs(item, ownerById, into)\n return\n }\n if (isPlainObject(value)) {\n for (const v of Object.values(value)) collectIdRefs(v, ownerById, into)\n }\n}\n\nfunction topoSort(entities: string[], edges: Map<string, Set<string>>): string[] {\n const indegree = new Map<string, number>(entities.map((e) => [e, 0]))\n const dependents = new Map<string, string[]>(entities.map((e) => [e, []]))\n for (const [entity, deps] of edges) {\n for (const dep of deps) {\n indegree.set(entity, (indegree.get(entity) ?? 0) + 1)\n dependents.get(dep)?.push(entity)\n }\n }\n\n const queue = entities.filter((e) => (indegree.get(e) ?? 0) === 0)\n const ordered: string[] = []\n while (queue.length > 0) {\n const entity = queue.shift() as string\n ordered.push(entity)\n for (const dependent of dependents.get(entity) ?? []) {\n const next = (indegree.get(dependent) ?? 0) - 1\n indegree.set(dependent, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (ordered.length !== entities.length) {\n const cyclic = entities.filter((e) => !ordered.includes(e))\n throw new GraphError(`Reference cycle between entities: ${cyclic.join(', ')}.`)\n }\n return ordered\n}\n\nexport function buildWritePlan(drained: DrainedEntries): WritePlan {\n const { refIndex, ids } = buildRefIndex(drained)\n const ownerById = ownerByIdOf(refIndex)\n const entities = drained.map((d) => d.entity)\n\n const records = new Map<string, SinkRecord[]>()\n const edges = new Map<string, Set<string>>(entities.map((e) => [e, new Set<string>()]))\n\n setActiveRefIndex(refIndex)\n try {\n for (const { entity, entries } of drained) {\n const out: SinkRecord[] = []\n entries.forEach((entry, i) => {\n const ctx: Ctx = {\n index: i,\n count: entries.length,\n ref: refById,\n refs: refsByEntity,\n }\n const payload = resolveValue(entry.value, ctx) as Record<string, unknown>\n const id = ids.get(entry) as string\n\n const referenced = new Set<string>()\n collectIdRefs(payload, ownerById, referenced)\n for (const dep of referenced) {\n if (dep !== entity) edges.get(entity)?.add(dep)\n }\n\n out.push({ ...payload, id })\n })\n records.set(entity, out)\n }\n } finally {\n setActiveRefIndex(undefined)\n }\n\n return { order: topoSort(entities, edges), records }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nconst DATA_DIR = 'data'\n\nfunction isDataFile(name: string): boolean {\n return name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')\n}\n\nexport async function discoverDataFiles(projectRoot: string): Promise<string[]> {\n const root = join(projectRoot, DATA_DIR)\n let names: string[]\n try {\n names = await readdir(root, { recursive: true })\n } catch {\n return []\n }\n return names\n .filter(isDataFile)\n .sort()\n .map((name) => join(root, name))\n}\n","import { type DrainedEntries, drain, resetRegistry } from '../define'\nimport { loadModule } from '../runtime'\n\nexport async function evaluateDataFiles(files: string[]): Promise<DrainedEntries> {\n resetRegistry()\n for (const file of files) {\n await loadModule(file)\n }\n return drain()\n}\n","import { createHash } from 'node:crypto'\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { ConfigError } from '../config'\n\nconst MANIFEST_DIR = '.fakeware'\nconst CURRENT_VERSION = 1 as const\n\nfunction shopKey(shopwareUrl: string): string {\n return createHash('sha256').update(shopwareUrl).digest('hex').slice(0, 16)\n}\n\nexport interface ManifestRecord {\n id: string\n hash: string\n}\n\nexport interface ManifestEntity {\n entity: string\n records: ManifestRecord[]\n}\n\nexport interface Manifest {\n version: 1\n fakewareVersion: string\n createdAt: string\n shopwareUrl: string\n entities: ManifestEntity[]\n checksum: string\n}\n\nexport function manifestPath(projectRoot: string, shopwareUrl: string): string {\n return join(projectRoot, MANIFEST_DIR, `${shopKey(shopwareUrl)}.json`)\n}\n\nfunction checksumOf(entities: ManifestEntity[]): string {\n const canonical = entities\n .map((e) => ({\n entity: e.entity,\n records: [...e.records].sort((a, b) => a.id.localeCompare(b.id)),\n }))\n .sort((a, b) => a.entity.localeCompare(b.entity))\n return createHash('sha256').update(JSON.stringify(canonical)).digest('hex')\n}\n\nexport interface BuildManifestInput {\n fakewareVersion: string\n createdAt: string\n shopwareUrl: string\n entities: ManifestEntity[]\n}\n\nexport function buildManifest(input: BuildManifestInput): Manifest {\n return {\n version: CURRENT_VERSION,\n fakewareVersion: input.fakewareVersion,\n createdAt: input.createdAt,\n shopwareUrl: input.shopwareUrl,\n entities: input.entities,\n checksum: checksumOf(input.entities),\n }\n}\n\nexport async function readManifest(\n projectRoot: string,\n shopwareUrl: string,\n): Promise<Manifest | null> {\n const path = manifestPath(projectRoot, shopwareUrl)\n let contents: string\n try {\n contents = await readFile(path, 'utf8')\n } catch {\n return null\n }\n const parsed = JSON.parse(contents) as { version?: number }\n switch (parsed.version) {\n case CURRENT_VERSION: {\n const manifest = parsed as Manifest\n if (checksumOf(manifest.entities) !== manifest.checksum) {\n throw new ConfigError(\n `Manifest at ${path} is corrupt (checksum mismatch). Re-run \\`fakeware up\\`.`,\n )\n }\n return manifest\n }\n default:\n throw new ConfigError(\n `Unsupported manifest version ${parsed.version} (this CLI understands up to ${CURRENT_VERSION}). Upgrade fakeware.`,\n )\n }\n}\n\nexport async function writeManifest(projectRoot: string, manifest: Manifest): Promise<void> {\n const path = manifestPath(projectRoot, manifest.shopwareUrl)\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, `${JSON.stringify(manifest, null, 2)}\\n`)\n}\n\nexport async function removeManifest(projectRoot: string, shopwareUrl: string): Promise<void> {\n await rm(manifestPath(projectRoot, shopwareUrl), { force: true })\n}\n","import type { LoadedConfig } from '../config'\nimport { recordHash } from '../define'\nimport type { ShopwareSink, SinkRecord } from '../domain'\nimport { buildWritePlan } from './build-graph'\nimport { discoverDataFiles } from './discover'\nimport { evaluateDataFiles } from './evaluate'\nimport {\n buildManifest,\n type Manifest,\n type ManifestEntity,\n readManifest,\n removeManifest,\n writeManifest,\n} from './manifest'\n\nexport interface Reporter {\n onStart?(entity: string): void\n onStep?(step: ReportStep): void\n}\n\nexport interface ReportStep {\n entity: string\n created: number\n updated: number\n unchanged: number\n deleted: number\n}\n\nexport interface RunOptions {\n loaded: LoadedConfig\n sink: ShopwareSink\n dryRun?: boolean\n reporter?: Reporter\n fakewareVersion?: string\n now?: string\n}\n\nexport interface UpResult {\n steps: ReportStep[]\n manifestWritten: boolean\n}\n\nexport interface DownResult {\n steps: ReportStep[]\n reverted: boolean\n}\n\nfunction priorHashes(manifest: Manifest | null): Map<string, Map<string, string>> {\n const map = new Map<string, Map<string, string>>()\n for (const e of manifest?.entities ?? []) {\n map.set(e.entity, new Map(e.records.map((r) => [r.id, r.hash])))\n }\n return map\n}\n\nexport async function runUp(opts: RunOptions): Promise<UpResult> {\n const { loaded, sink, dryRun, reporter } = opts\n const files = await discoverDataFiles(loaded.projectRoot)\n const drained = await evaluateDataFiles(files)\n const plan = buildWritePlan(drained)\n\n const prior = priorHashes(await readManifest(loaded.projectRoot, loaded.connection.url))\n const steps: ReportStep[] = []\n const manifestEntities: ManifestEntity[] = []\n\n for (const entity of plan.order) {\n reporter?.onStart?.(entity)\n const records = plan.records.get(entity) ?? []\n const priorForEntity = prior.get(entity) ?? new Map<string, string>()\n const toWrite: SinkRecord[] = []\n let created = 0\n let updated = 0\n let unchanged = 0\n const manifestRecords = records.map((record) => {\n const hash = recordHash(record)\n const previous = priorForEntity.get(record.id)\n if (previous === undefined) created++\n else if (previous === hash) unchanged++\n else updated++\n if (previous !== hash) toWrite.push(record)\n return { id: record.id, hash }\n })\n\n if (!dryRun && toWrite.length > 0) {\n await sink.upsert(entity, toWrite)\n }\n\n manifestEntities.push({ entity, records: manifestRecords })\n const step: ReportStep = { entity, created, updated, unchanged, deleted: 0 }\n steps.push(step)\n reporter?.onStep?.(step)\n }\n\n if (!dryRun) {\n await writeManifest(\n loaded.projectRoot,\n buildManifest({\n fakewareVersion: opts.fakewareVersion ?? '0.0.0',\n createdAt: opts.now ?? new Date().toISOString(),\n shopwareUrl: loaded.connection.url,\n entities: manifestEntities,\n }),\n )\n }\n\n return { steps, manifestWritten: !dryRun }\n}\n\nexport async function runDown(opts: RunOptions): Promise<DownResult> {\n const { loaded, sink, dryRun, reporter } = opts\n const manifest = await readManifest(loaded.projectRoot, loaded.connection.url)\n if (!manifest) return { steps: [], reverted: false }\n\n const steps: ReportStep[] = []\n for (const entity of [...manifest.entities].reverse()) {\n reporter?.onStart?.(entity.entity)\n const ids = entity.records.map((r) => r.id)\n if (!dryRun && ids.length > 0) {\n await sink.delete(entity.entity, ids)\n }\n const step: ReportStep = {\n entity: entity.entity,\n created: 0,\n updated: 0,\n unchanged: 0,\n deleted: ids.length,\n }\n steps.push(step)\n reporter?.onStep?.(step)\n }\n\n if (!dryRun) await removeManifest(loaded.projectRoot, loaded.connection.url)\n return { steps, reverted: !dryRun }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["refById","refsByEntity"],"sources":["../src/define/errors.ts","../src/define/is-plain-object.ts","../src/define/ids.ts","../src/define/registry.ts","../src/define/define.ts","../src/define/resolve.ts","../src/engine/errors.ts","../src/engine/build-graph.ts","../src/engine/discover.ts","../src/engine/evaluate.ts","../src/engine/manifest.ts","../src/engine/run.ts"],"sourcesContent":["export class RefError extends Error {}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)\n )\n}\n","import { createHash } from 'node:crypto'\nimport { isPlainObject } from './is-plain-object'\n\nconst FAKEWARE_NAMESPACE = 'b1e0a3f4-6c2d-5a8b-9e7f-0d1c2b3a4e5f'\n\nfunction uuidBytes(uuid: string): Uint8Array {\n const hex = uuid.replace(/-/g, '')\n const bytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return bytes\n}\n\nconst NAMESPACE_BYTES = uuidBytes(FAKEWARE_NAMESPACE)\n\nfunction uuidv5(name: string): string {\n const hash = createHash('sha1')\n hash.update(NAMESPACE_BYTES)\n hash.update(name, 'utf8')\n const digest = hash.digest()\n\n const bytes = digest.subarray(0, 16)\n bytes[6] = ((bytes[6] as number) & 0x0f) | 0x50\n bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80\n\n return bytes.toString('hex')\n}\n\nexport function deterministicId(entity: string, key: string): string {\n return uuidv5(`${entity}:${key}`)\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize)\n if (isPlainObject(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value).sort()) {\n sorted[k] = canonicalize(value[k])\n }\n return sorted\n }\n return value\n}\n\nexport function recordHash(payload: unknown): string {\n return createHash('sha256')\n .update(JSON.stringify(canonicalize(payload)))\n .digest('hex')\n}\n","import type { Ctx } from './ctx'\nimport { deterministicId } from './ids'\n\ntype RecordObject = Record<string, unknown>\nexport type RecordValue = RecordObject | ((ctx: Ctx) => RecordObject)\n\ninterface RawEntry {\n entity: string\n key?: string\n value: RecordValue\n}\n\nexport type DrainedEntries = { entity: string; entries: RawEntry[] }[]\n\nexport interface RefIndex {\n byEntity: Map<string, { byKey: Map<string, string>; all: string[] }>\n}\n\nlet entries: RawEntry[] = []\n\nexport function resetRegistry(): void {\n entries = []\n}\n\nfunction staticKey(value: RecordValue): string | undefined {\n if (typeof value !== 'function') {\n const k = (value as RecordObject).$key\n if (typeof k === 'string') return k\n }\n return undefined\n}\n\nexport function defineRecords(entity: string, recordOrRecords: RecordValue | RecordValue[]): void {\n const list = Array.isArray(recordOrRecords) ? recordOrRecords : [recordOrRecords]\n for (const value of list) {\n entries.push({ entity, key: staticKey(value), value })\n }\n}\n\nexport function drain(): DrainedEntries {\n const order: string[] = []\n const byEntity = new Map<string, RawEntry[]>()\n for (const e of entries) {\n let bucket = byEntity.get(e.entity)\n if (!bucket) {\n bucket = []\n byEntity.set(e.entity, bucket)\n order.push(e.entity)\n }\n bucket.push(e)\n }\n return order.map((entity) => ({ entity, entries: byEntity.get(entity) as RawEntry[] }))\n}\n\nexport function buildRefIndex(drained: DrainedEntries): {\n refIndex: RefIndex\n ids: Map<RawEntry, string>\n} {\n const refIndex: RefIndex = { byEntity: new Map() }\n const ids = new Map<RawEntry, string>()\n\n for (const { entity, entries: bucket } of drained) {\n const slot = { byKey: new Map<string, string>(), all: [] as string[] }\n refIndex.byEntity.set(entity, slot)\n bucket.forEach((entry, i) => {\n const idKey = entry.key ?? String(i)\n const id = deterministicId(entity, idKey)\n ids.set(entry, id)\n slot.all.push(id)\n if (entry.key) slot.byKey.set(entry.key, id)\n })\n }\n\n return { refIndex, ids }\n}\n\nexport type { RawEntry }\n","import type { Ctx } from './ctx'\nimport { RefError } from './errors'\nimport { defineRecords, type RecordValue, type RefIndex } from './registry'\nimport type { DefineRecord, EntityName } from './schema'\n\nexport function define<const E extends EntityName>(\n entity: E,\n records: DefineRecord<E> | readonly DefineRecord<E>[],\n): void {\n defineRecords(entity, records as RecordValue | RecordValue[])\n}\n\nexport function many<R extends Record<string, unknown>>(n: number, fn: (ctx: Ctx) => R): R {\n return Array.from({ length: n }, () => fn) as unknown as R\n}\n\nlet active: RefIndex | undefined\n\nexport function setActiveRefIndex(refIndex: RefIndex | undefined): void {\n active = refIndex\n}\n\nfunction requireActive(): RefIndex {\n if (!active) {\n throw new RefError('ref()/refs() may only be called while resolving definitions.')\n }\n return active\n}\n\nexport function ref(path: string): string {\n const slash = path.indexOf('/')\n if (slash === -1) {\n throw new RefError(`ref('${path}') must be of the form 'entity/key'.`)\n }\n const entity = path.slice(0, slash)\n const key = path.slice(slash + 1)\n const id = requireActive().byEntity.get(entity)?.byKey.get(key)\n if (!id) {\n throw new RefError(`ref('${path}') does not match any defined record.`)\n }\n return id\n}\n\nexport function refs(entity: string): string[] {\n const slot = requireActive().byEntity.get(entity)\n if (!slot) {\n throw new RefError(`refs('${entity}') does not match any defined entity.`)\n }\n return [...slot.all]\n}\n","import type { Ctx } from './ctx'\nimport { isPlainObject } from './is-plain-object'\n\nexport function resolveValue(value: unknown, ctx: Ctx): unknown {\n if (typeof value === 'function') {\n return resolveValue((value as (ctx: Ctx) => unknown)(ctx), ctx)\n }\n if (Array.isArray(value)) {\n return value.map((item) => resolveValue(item, ctx))\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {}\n for (const [key, v] of Object.entries(value)) {\n if (key === '$key') continue\n out[key] = resolveValue(v, ctx)\n }\n return out\n }\n return value\n}\n","import type { ReportStep } from './run'\n\nexport class GraphError extends Error {}\n\nexport class TransactionError extends Error {\n readonly rolledBack: ReportStep[]\n readonly failedEntity: string\n readonly unrevertableUpdates: boolean\n readonly compensationErrors: unknown[]\n\n constructor(\n message: string,\n options: {\n cause: unknown\n rolledBack: ReportStep[]\n failedEntity: string\n unrevertableUpdates?: boolean\n compensationErrors?: unknown[]\n },\n ) {\n super(message, { cause: options.cause })\n this.name = 'TransactionError'\n this.rolledBack = options.rolledBack\n this.failedEntity = options.failedEntity\n this.unrevertableUpdates = options.unrevertableUpdates ?? false\n this.compensationErrors = options.compensationErrors ?? []\n }\n}\n","import {\n buildRefIndex,\n type Ctx,\n type DrainedEntries,\n isPlainObject,\n type RefIndex,\n ref as refById,\n refs as refsByEntity,\n resolveValue,\n setActiveRefIndex,\n} from '../define'\nimport type { SinkRecord } from '../domain'\nimport { GraphError } from './errors'\n\nexport interface WritePlan {\n order: string[]\n records: Map<string, SinkRecord[]>\n}\n\nfunction ownerByIdOf(refIndex: RefIndex): Map<string, string> {\n const owner = new Map<string, string>()\n for (const [entity, slot] of refIndex.byEntity) {\n for (const id of slot.all) owner.set(id, entity)\n }\n return owner\n}\n\nfunction collectIdRefs(value: unknown, ownerById: Map<string, string>, into: Set<string>): void {\n if (typeof value === 'string') {\n const owner = ownerById.get(value)\n if (owner) into.add(owner)\n return\n }\n if (Array.isArray(value)) {\n for (const item of value) collectIdRefs(item, ownerById, into)\n return\n }\n if (isPlainObject(value)) {\n for (const v of Object.values(value)) collectIdRefs(v, ownerById, into)\n }\n}\n\nfunction topoSort(entities: string[], edges: Map<string, Set<string>>): string[] {\n const indegree = new Map<string, number>(entities.map((e) => [e, 0]))\n const dependents = new Map<string, string[]>(entities.map((e) => [e, []]))\n for (const [entity, deps] of edges) {\n for (const dep of deps) {\n indegree.set(entity, (indegree.get(entity) ?? 0) + 1)\n dependents.get(dep)?.push(entity)\n }\n }\n\n const queue = entities.filter((e) => (indegree.get(e) ?? 0) === 0)\n const ordered: string[] = []\n while (queue.length > 0) {\n const entity = queue.shift() as string\n ordered.push(entity)\n for (const dependent of dependents.get(entity) ?? []) {\n const next = (indegree.get(dependent) ?? 0) - 1\n indegree.set(dependent, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (ordered.length !== entities.length) {\n const cyclic = entities.filter((e) => !ordered.includes(e))\n throw new GraphError(`Reference cycle between entities: ${cyclic.join(', ')}.`)\n }\n return ordered\n}\n\nexport function buildWritePlan(drained: DrainedEntries): WritePlan {\n const { refIndex, ids } = buildRefIndex(drained)\n const ownerById = ownerByIdOf(refIndex)\n const entities = drained.map((d) => d.entity)\n\n const records = new Map<string, SinkRecord[]>()\n const edges = new Map<string, Set<string>>(entities.map((e) => [e, new Set<string>()]))\n\n setActiveRefIndex(refIndex)\n try {\n for (const { entity, entries } of drained) {\n const out: SinkRecord[] = []\n entries.forEach((entry, i) => {\n const ctx: Ctx = {\n index: i,\n count: entries.length,\n ref: refById,\n refs: refsByEntity,\n }\n const payload = resolveValue(entry.value, ctx) as Record<string, unknown>\n const id = ids.get(entry) as string\n\n const referenced = new Set<string>()\n collectIdRefs(payload, ownerById, referenced)\n for (const dep of referenced) {\n if (dep !== entity) edges.get(entity)?.add(dep)\n }\n\n out.push({ ...payload, id })\n })\n records.set(entity, out)\n }\n } finally {\n setActiveRefIndex(undefined)\n }\n\n return { order: topoSort(entities, edges), records }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nconst DATA_DIR = 'data'\n\nfunction isDataFile(name: string): boolean {\n return name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')\n}\n\nexport async function discoverDataFiles(projectRoot: string): Promise<string[]> {\n const root = join(projectRoot, DATA_DIR)\n let names: string[]\n try {\n names = await readdir(root, { recursive: true })\n } catch {\n return []\n }\n return names\n .filter(isDataFile)\n .sort()\n .map((name) => join(root, name))\n}\n","import { type DrainedEntries, drain, resetRegistry } from '../define'\nimport { loadModule } from '../runtime'\n\nexport async function evaluateDataFiles(files: string[]): Promise<DrainedEntries> {\n resetRegistry()\n for (const file of files) {\n await loadModule(file)\n }\n return drain()\n}\n","import { createHash } from 'node:crypto'\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { ConfigError } from '../config'\n\nconst MANIFEST_DIR = '.fakeware'\nconst CURRENT_VERSION = 1 as const\n\nfunction shopKey(shopwareUrl: string): string {\n return createHash('sha256').update(shopwareUrl).digest('hex').slice(0, 16)\n}\n\nexport interface ManifestRecord {\n id: string\n hash: string\n}\n\nexport interface ManifestEntity {\n entity: string\n records: ManifestRecord[]\n}\n\nexport interface Manifest {\n version: 1\n fakewareVersion: string\n createdAt: string\n shopwareUrl: string\n entities: ManifestEntity[]\n checksum: string\n}\n\nexport function manifestPath(projectRoot: string, shopwareUrl: string): string {\n return join(projectRoot, MANIFEST_DIR, `${shopKey(shopwareUrl)}.json`)\n}\n\nfunction checksumOf(entities: ManifestEntity[]): string {\n const canonical = entities\n .map((e) => ({\n entity: e.entity,\n records: [...e.records].sort((a, b) => a.id.localeCompare(b.id)),\n }))\n .sort((a, b) => a.entity.localeCompare(b.entity))\n return createHash('sha256').update(JSON.stringify(canonical)).digest('hex')\n}\n\nexport interface BuildManifestInput {\n fakewareVersion: string\n createdAt: string\n shopwareUrl: string\n entities: ManifestEntity[]\n}\n\nexport function buildManifest(input: BuildManifestInput): Manifest {\n return {\n version: CURRENT_VERSION,\n fakewareVersion: input.fakewareVersion,\n createdAt: input.createdAt,\n shopwareUrl: input.shopwareUrl,\n entities: input.entities,\n checksum: checksumOf(input.entities),\n }\n}\n\nexport async function readManifest(\n projectRoot: string,\n shopwareUrl: string,\n): Promise<Manifest | null> {\n const path = manifestPath(projectRoot, shopwareUrl)\n let contents: string\n try {\n contents = await readFile(path, 'utf8')\n } catch {\n return null\n }\n const parsed = JSON.parse(contents) as { version?: number }\n switch (parsed.version) {\n case CURRENT_VERSION: {\n const manifest = parsed as Manifest\n if (checksumOf(manifest.entities) !== manifest.checksum) {\n throw new ConfigError(\n `Manifest at ${path} is corrupt (checksum mismatch). Re-run \\`fakeware up\\`.`,\n )\n }\n return manifest\n }\n default:\n throw new ConfigError(\n `Unsupported manifest version ${parsed.version} (this CLI understands up to ${CURRENT_VERSION}). Upgrade fakeware.`,\n )\n }\n}\n\nexport async function writeManifest(projectRoot: string, manifest: Manifest): Promise<void> {\n const path = manifestPath(projectRoot, manifest.shopwareUrl)\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, `${JSON.stringify(manifest, null, 2)}\\n`)\n}\n\nexport async function removeManifest(projectRoot: string, shopwareUrl: string): Promise<void> {\n await rm(manifestPath(projectRoot, shopwareUrl), { force: true })\n}\n","import type { LoadedConfig } from '../config'\nimport { recordHash } from '../define'\nimport type { BatchProgress, ShopwareSink, SinkRecord, SyncOperation } from '../domain'\nimport { ATOMIC_REQUEST_BYTE_LIMIT, estimateSyncBytes } from '../shopware'\nimport { buildWritePlan } from './build-graph'\nimport { discoverDataFiles } from './discover'\nimport { TransactionError } from './errors'\nimport { evaluateDataFiles } from './evaluate'\nimport {\n buildManifest,\n type Manifest,\n type ManifestEntity,\n type ManifestRecord,\n readManifest,\n removeManifest,\n writeManifest,\n} from './manifest'\n\nexport interface Reporter {\n onStart?(entity: string, records?: number): void\n onBatch?(progress: BatchProgress): void\n onStep?(step: ReportStep): void\n onTransactionStart?(info: { mode: 'atomic' | 'saga' }): void\n onCommit?(info: { committed: number }): void\n onCompensate?(entity: string, count: number): void\n onCompensateFail?(entity: string): void\n onSkip?(info: { entity: string; error: unknown }): void\n onStop?(info: { failedEntity: string; error: unknown; message: string }): void\n}\n\nexport interface ReportStep {\n entity: string\n created: number\n updated: number\n unchanged: number\n deleted: number\n}\n\nexport type OnError = 'rollback' | 'continue' | 'stop'\n\nexport interface TransactionOptions {\n onError: OnError\n atomic: boolean\n}\n\nexport interface RunOptions {\n loaded: LoadedConfig\n sink: ShopwareSink\n dryRun?: boolean\n reporter?: Reporter\n fakewareVersion?: string\n now?: string\n transaction?: TransactionOptions\n}\n\nexport interface UpResult {\n steps: ReportStep[]\n manifestWritten: boolean\n mode: 'atomic' | 'saga' | 'dry-run' | 'noop'\n committed: number\n rolledBack: number\n}\n\nexport interface DownResult {\n steps: ReportStep[]\n reverted: boolean\n}\n\ninterface EntityWrite {\n entity: string\n toWrite: SinkRecord[]\n createdIds: string[]\n manifestRecords: ManifestRecord[]\n step: ReportStep\n}\n\nfunction priorHashes(manifest: Manifest | null): Map<string, Map<string, string>> {\n const map = new Map<string, Map<string, string>>()\n for (const e of manifest?.entities ?? []) {\n map.set(e.entity, new Map(e.records.map((r) => [r.id, r.hash])))\n }\n return map\n}\n\nfunction resolveTransaction(opts: RunOptions): TransactionOptions {\n return opts.transaction ?? opts.loaded.config.transaction\n}\n\nfunction diffEntity(\n entity: string,\n records: SinkRecord[],\n prior: Map<string, string>,\n): EntityWrite {\n const toWrite: SinkRecord[] = []\n const createdIds: string[] = []\n let created = 0\n let updated = 0\n let unchanged = 0\n const manifestRecords = records.map((record) => {\n const hash = recordHash(record)\n const previous = prior.get(record.id)\n if (previous === undefined) {\n created++\n createdIds.push(record.id)\n } else if (previous === hash) {\n unchanged++\n } else {\n updated++\n }\n if (previous !== hash) toWrite.push(record)\n return { id: record.id, hash }\n })\n return {\n entity,\n toWrite,\n createdIds,\n manifestRecords,\n step: { entity, created, updated, unchanged, deleted: 0 },\n }\n}\n\nfunction partialWrite(w: EntityWrite, committed: number): EntityWrite | null {\n if (committed <= 0) return null\n const createdSet = new Set(w.createdIds)\n const prefix = w.toWrite.slice(0, committed)\n const createdIds = prefix.map((r) => r.id).filter((id) => createdSet.has(id))\n const updated = prefix.length - createdIds.length\n if (createdIds.length === 0 && updated === 0) return null\n return {\n entity: w.entity,\n toWrite: [],\n createdIds,\n manifestRecords: [],\n step: { entity: w.entity, created: 0, updated, unchanged: 0, deleted: 0 },\n }\n}\n\nexport async function runUp(opts: RunOptions): Promise<UpResult> {\n const { loaded, sink, dryRun, reporter } = opts\n const tx = resolveTransaction(opts)\n const files = await discoverDataFiles(loaded.projectRoot)\n const drained = await evaluateDataFiles(files)\n const plan = buildWritePlan(drained)\n\n const prior = priorHashes(await readManifest(loaded.projectRoot, loaded.connection.url))\n\n const writes: EntityWrite[] = plan.order.map((entity) =>\n diffEntity(\n entity,\n plan.records.get(entity) ?? [],\n prior.get(entity) ?? new Map<string, string>(),\n ),\n )\n\n const steps = writes.map((w) => w.step)\n const manifestEntities: ManifestEntity[] = writes.map((w) => ({\n entity: w.entity,\n records: w.manifestRecords,\n }))\n const committed = steps.reduce((n, s) => n + s.created + s.updated, 0)\n const pending = writes.filter((w) => w.toWrite.length > 0)\n\n if (dryRun || pending.length === 0) {\n for (const w of writes) {\n reporter?.onStart?.(w.entity)\n reporter?.onStep?.(w.step)\n }\n return {\n steps,\n manifestWritten: false,\n mode: dryRun ? 'dry-run' : 'noop',\n committed: 0,\n rolledBack: 0,\n }\n }\n\n const writeManifestNow = (entities: ManifestEntity[]): Promise<void> =>\n writeManifest(\n loaded.projectRoot,\n buildManifest({\n fakewareVersion: opts.fakewareVersion ?? '0.0.0',\n createdAt: opts.now ?? new Date().toISOString(),\n shopwareUrl: loaded.connection.url,\n entities,\n }),\n )\n\n const operations: SyncOperation[] = pending.map((w) => ({\n entity: w.entity,\n action: 'upsert',\n records: w.toWrite,\n }))\n\n if (tx.atomic && estimateSyncBytes(operations) <= ATOMIC_REQUEST_BYTE_LIMIT) {\n reporter?.onTransactionStart?.({ mode: 'atomic' })\n try {\n await sink.applyAtomic(operations)\n } catch (error) {\n const message = 'Apply failed — Shopware rolled back all changes.'\n reporter?.onStop?.({ failedEntity: '', error, message })\n throw new TransactionError(message, {\n cause: error,\n rolledBack: [],\n failedEntity: '',\n })\n }\n await writeManifestNow(manifestEntities)\n reporter?.onCommit?.({ committed })\n return { steps, manifestWritten: true, mode: 'atomic', committed, rolledBack: 0 }\n }\n\n reporter?.onTransactionStart?.({ mode: 'saga' })\n const written: EntityWrite[] = []\n const skipped: { entity: string; error: unknown }[] = []\n\n for (const w of pending) {\n reporter?.onStart?.(w.entity, w.toWrite.length)\n let committedRecords = 0\n try {\n await sink.upsert(w.entity, w.toWrite, (progress) => {\n committedRecords = progress.records\n reporter?.onBatch?.(progress)\n })\n written.push(w)\n reporter?.onStep?.(w.step)\n } catch (error) {\n if (tx.onError === 'stop') {\n const message = `Writing ${w.entity} failed — stopped.`\n reporter?.onStop?.({ failedEntity: w.entity, error, message })\n throw new TransactionError(message, {\n cause: error,\n rolledBack: [],\n failedEntity: w.entity,\n })\n }\n if (tx.onError === 'continue') {\n skipped.push({ entity: w.entity, error })\n reporter?.onSkip?.({ entity: w.entity, error })\n continue\n }\n const message = `Could not apply ${w.entity}.`\n reporter?.onStop?.({ failedEntity: w.entity, error, message })\n const partial = partialWrite(w, committedRecords)\n const toCompensate = partial ? [...written, partial] : written\n const { rolledBack, unrevertableUpdates, compensationErrors } = await compensate(\n sink,\n toCompensate,\n reporter,\n )\n throw new TransactionError(message, {\n cause: error,\n rolledBack,\n failedEntity: w.entity,\n unrevertableUpdates,\n compensationErrors,\n })\n }\n }\n\n for (const w of writes) {\n if (!written.includes(w) && !skipped.some((s) => s.entity === w.entity)) {\n reporter?.onStart?.(w.entity)\n reporter?.onStep?.(w.step)\n }\n }\n\n if (skipped.length > 0) {\n const writtenEntities = new Set(written.map((w) => w.entity))\n await writeManifestNow(restrictManifest(manifestEntities, prior, writtenEntities))\n const first = skipped[0] as { entity: string; error: unknown }\n throw new TransactionError(\n `Applied with ${skipped.length} skipped entit${skipped.length === 1 ? 'y' : 'ies'}.`,\n { cause: skipped, rolledBack: [], failedEntity: first.entity },\n )\n }\n\n await writeManifestNow(manifestEntities)\n reporter?.onCommit?.({ committed })\n return { steps, manifestWritten: true, mode: 'saga', committed, rolledBack: 0 }\n}\n\nasync function compensate(\n sink: ShopwareSink,\n written: EntityWrite[],\n reporter?: Reporter,\n): Promise<{\n rolledBack: ReportStep[]\n unrevertableUpdates: boolean\n compensationErrors: unknown[]\n}> {\n const rolledBack: ReportStep[] = []\n const compensationErrors: unknown[] = []\n for (const w of [...written].reverse()) {\n if (w.createdIds.length === 0) continue\n try {\n await sink.delete(w.entity, w.createdIds)\n rolledBack.push({\n entity: w.entity,\n created: 0,\n updated: 0,\n unchanged: 0,\n deleted: w.createdIds.length,\n })\n reporter?.onCompensate?.(w.entity, w.createdIds.length)\n } catch (error) {\n compensationErrors.push(error)\n reporter?.onCompensateFail?.(w.entity)\n }\n }\n const unrevertableUpdates = written.some((w) => w.step.updated > 0)\n return { rolledBack, unrevertableUpdates, compensationErrors }\n}\n\nfunction restrictManifest(\n desired: ManifestEntity[],\n prior: Map<string, Map<string, string>>,\n writtenEntities: Set<string>,\n): ManifestEntity[] {\n const out: ManifestEntity[] = []\n const seen = new Set<string>()\n for (const e of desired) {\n seen.add(e.entity)\n if (writtenEntities.has(e.entity)) {\n out.push(e)\n } else {\n const priorRecords = prior.get(e.entity)\n if (priorRecords && priorRecords.size > 0) {\n out.push({\n entity: e.entity,\n records: [...priorRecords].map(([id, hash]) => ({ id, hash })),\n })\n }\n }\n }\n for (const [entity, records] of prior) {\n if (!seen.has(entity) && records.size > 0) {\n out.push({ entity, records: [...records].map(([id, hash]) => ({ id, hash })) })\n }\n }\n return out\n}\n\nexport async function runDown(opts: RunOptions): Promise<DownResult> {\n const { loaded, sink, dryRun, reporter } = opts\n const manifest = await readManifest(loaded.projectRoot, loaded.connection.url)\n if (!manifest) return { steps: [], reverted: false }\n\n reporter?.onTransactionStart?.({ mode: 'saga' })\n\n const steps: ReportStep[] = []\n for (const entity of [...manifest.entities].reverse()) {\n const ids = entity.records.map((r) => r.id)\n reporter?.onStart?.(entity.entity, ids.length)\n if (!dryRun && ids.length > 0) {\n await sink.delete(entity.entity, ids, (progress) => reporter?.onBatch?.(progress))\n }\n const step: ReportStep = {\n entity: entity.entity,\n created: 0,\n updated: 0,\n unchanged: 0,\n deleted: ids.length,\n }\n steps.push(step)\n reporter?.onStep?.(step)\n }\n\n if (!dryRun) await removeManifest(loaded.projectRoot, loaded.connection.url)\n return { steps, reverted: !dryRun }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type ShopInfo, type ShopwareClient, type ShopwareConnection, ShopwareConnectionError, createShopwareClient, createSyncSink, fetchShopInfo, toConnectionError, validateConnection };
|
|
1
|
+
import { c as fetchShopInfo, d as ShopwareConnectionError, f as ShopwareClient, h as ShopwareConnection, l as toConnectionError, m as ShopInfo, n as createSyncSink, p as createShopwareClient, r as estimateSyncBytes, t as ATOMIC_REQUEST_BYTE_LIMIT, u as validateConnection } from "../index-Brciwig_.mjs";
|
|
2
|
+
export { ATOMIC_REQUEST_BYTE_LIMIT, type ShopInfo, type ShopwareClient, type ShopwareConnection, ShopwareConnectionError, createShopwareClient, createSyncSink, estimateSyncBytes, fetchShopInfo, toConnectionError, validateConnection };
|
package/dist/shopware/index.mjs
CHANGED
|
@@ -1,158 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/shopware/client.ts
|
|
4
|
-
const REQUEST_TIMEOUT_MS = 12e4;
|
|
5
|
-
function createShopwareClient(connection) {
|
|
6
|
-
return createAdminAPIClient({
|
|
7
|
-
baseURL: `${connection.url.replace(/\/$/, "")}/api`,
|
|
8
|
-
credentials: {
|
|
9
|
-
grant_type: "client_credentials",
|
|
10
|
-
client_id: connection.clientId,
|
|
11
|
-
client_secret: connection.clientSecret
|
|
12
|
-
},
|
|
13
|
-
fetchOptions: { timeout: REQUEST_TIMEOUT_MS }
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/shopware/errors.ts
|
|
18
|
-
var ShopwareConnectionError = class extends Error {};
|
|
19
|
-
//#endregion
|
|
20
|
-
//#region src/shopware/locale.ts
|
|
21
|
-
const SYSTEM_LANGUAGE_ID = "2fbb5fe2e29a4d70aa5854ce7ce3e20b";
|
|
22
|
-
const languageRowSchema = z.object({
|
|
23
|
-
id: z.string(),
|
|
24
|
-
locale: z.object({ code: z.string().optional() }).nullish()
|
|
25
|
-
});
|
|
26
|
-
function parseLanguageRows(rows) {
|
|
27
|
-
const result = z.array(languageRowSchema).safeParse(rows);
|
|
28
|
-
if (!result.success) throw new ShopwareConnectionError("Shopware returned an unexpected response shape for languages.");
|
|
29
|
-
return result.data;
|
|
30
|
-
}
|
|
31
|
-
function toShopInfo(rows) {
|
|
32
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33
|
-
const locales = [];
|
|
34
|
-
let systemLocale;
|
|
35
|
-
for (const row of rows) {
|
|
36
|
-
const code = row.locale?.code;
|
|
37
|
-
if (!code) continue;
|
|
38
|
-
if (row.id === SYSTEM_LANGUAGE_ID) systemLocale = code;
|
|
39
|
-
if (!seen.has(code)) {
|
|
40
|
-
seen.add(code);
|
|
41
|
-
locales.push(code);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (locales.length === 0) throw new ShopwareConnectionError("Shopware returned no usable locales.");
|
|
45
|
-
return {
|
|
46
|
-
locales,
|
|
47
|
-
defaultLocale: systemLocale ?? locales[0]
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
//#endregion
|
|
51
|
-
//#region src/shopware/operations.ts
|
|
52
|
-
function safeJsonParse(input) {
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(input);
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function isTimeoutError(error) {
|
|
60
|
-
let current = error;
|
|
61
|
-
while (current instanceof Error) {
|
|
62
|
-
if (current.name === "TimeoutError") return true;
|
|
63
|
-
current = current.cause;
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
function missingPrivileges(error) {
|
|
68
|
-
for (const e of error.details.errors) {
|
|
69
|
-
if (e.code !== "FRAMEWORK__MISSING_PRIVILEGE_ERROR" || !e.detail) continue;
|
|
70
|
-
const parsed = safeJsonParse(e.detail);
|
|
71
|
-
if (parsed?.missingPrivileges?.length) return parsed.missingPrivileges;
|
|
72
|
-
}
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
function validationMessages(error) {
|
|
76
|
-
return error.details.errors.map((e) => {
|
|
77
|
-
const field = e.source?.pointer?.replace(/^\/\d+\/\d+\//, "");
|
|
78
|
-
const detail = e.detail ?? e.title ?? "Invalid value.";
|
|
79
|
-
return field ? `${field}: ${detail}` : detail;
|
|
80
|
-
}).filter((message, index, all) => all.indexOf(message) === index);
|
|
81
|
-
}
|
|
82
|
-
function toConnectionError(connection, error) {
|
|
83
|
-
if (isTimeoutError(error)) return new ShopwareConnectionError(`${connection.url} did not respond within ${REQUEST_TIMEOUT_MS / 1e3}s, the shop may be slow or unreachable.`);
|
|
84
|
-
if (error instanceof ApiClientError) switch (error.status) {
|
|
85
|
-
case 400: {
|
|
86
|
-
const messages = validationMessages(error);
|
|
87
|
-
return new ShopwareConnectionError(messages.length ? `Shopware rejected the data — ${messages.join(" ")}` : `Shopware rejected the request (HTTP 400) from ${connection.url}.`);
|
|
88
|
-
}
|
|
89
|
-
case 401: return new ShopwareConnectionError("Authentication failed — check the client ID and client secret of your integration.");
|
|
90
|
-
case 403: {
|
|
91
|
-
const missing = missingPrivileges(error);
|
|
92
|
-
if (missing.length) return new ShopwareConnectionError(`The integration is missing the ${missing.join(", ")} ${missing.length === 1 ? "privilege" : "privileges"} — grant them to its role in Settings → System → Integrations.`);
|
|
93
|
-
return new ShopwareConnectionError("The integration is missing permissions — grant its role admin API access in Settings → System → Integrations.");
|
|
94
|
-
}
|
|
95
|
-
case 404: return new ShopwareConnectionError(`No Shopware admin API found at ${connection.url} — check the shop URL.`);
|
|
96
|
-
default:
|
|
97
|
-
if (error.status >= 500) return new ShopwareConnectionError(`${connection.url} is not responding (HTTP ${error.status}) — the shop may be down or in maintenance.`);
|
|
98
|
-
return new ShopwareConnectionError(`Shopware returned an unexpected response (HTTP ${error.status}) from ${connection.url}.`);
|
|
99
|
-
}
|
|
100
|
-
return new ShopwareConnectionError(`Could not reach ${connection.url} — check the URL and your network connection.`);
|
|
101
|
-
}
|
|
102
|
-
async function validateConnection(connection) {
|
|
103
|
-
const client = createShopwareClient(connection);
|
|
104
|
-
try {
|
|
105
|
-
await client.invoke("infoShopwareVersion get /_info/version");
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw toConnectionError(connection, error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
async function fetchShopInfo(connection) {
|
|
111
|
-
const client = createShopwareClient(connection);
|
|
112
|
-
try {
|
|
113
|
-
const { data } = await client.invoke("searchLanguage post /search/language", { body: {
|
|
114
|
-
associations: { locale: {} },
|
|
115
|
-
limit: 500
|
|
116
|
-
} });
|
|
117
|
-
return toShopInfo(parseLanguageRows(data.data ?? []));
|
|
118
|
-
} catch (error) {
|
|
119
|
-
throw toConnectionError(connection, error);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/shopware/sink.ts
|
|
124
|
-
const SYNC_BATCH_SIZE = 50;
|
|
125
|
-
function chunk(items, size) {
|
|
126
|
-
const out = [];
|
|
127
|
-
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
function createSyncSink(connection, options = {}) {
|
|
131
|
-
const client = options.client ?? createShopwareClient(connection);
|
|
132
|
-
async function sync(entity, action, payload) {
|
|
133
|
-
for (const batch of chunk(payload, SYNC_BATCH_SIZE)) try {
|
|
134
|
-
await client.invoke("sync post /_action/sync", {
|
|
135
|
-
headers: { "indexing-behavior": "use-queue-indexing" },
|
|
136
|
-
body: [{
|
|
137
|
-
entity,
|
|
138
|
-
action,
|
|
139
|
-
payload: batch
|
|
140
|
-
}]
|
|
141
|
-
});
|
|
142
|
-
} catch (error) {
|
|
143
|
-
throw toConnectionError(connection, error);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
async upsert(entity, records) {
|
|
148
|
-
if (records.length > 0) await sync(entity, "upsert", records);
|
|
149
|
-
},
|
|
150
|
-
async delete(entity, ids) {
|
|
151
|
-
if (ids.length > 0) await sync(entity, "delete", ids.map((id) => ({ id })));
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
//#endregion
|
|
156
|
-
export { ShopwareConnectionError, createShopwareClient, createSyncSink, fetchShopInfo, toConnectionError, validateConnection };
|
|
157
|
-
|
|
158
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { a as toConnectionError, c as createShopwareClient, i as fetchShopInfo, n as createSyncSink, o as validateConnection, r as estimateSyncBytes, s as ShopwareConnectionError, t as ATOMIC_REQUEST_BYTE_LIMIT } from "../shopware-CIZF8Nuo.mjs";
|
|
2
|
+
export { ATOMIC_REQUEST_BYTE_LIMIT, ShopwareConnectionError, createShopwareClient, createSyncSink, estimateSyncBytes, fetchShopInfo, toConnectionError, validateConnection };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiClientError, createAdminAPIClient } from "@shopware/api-client";
|
|
3
|
+
//#region src/shopware/client.ts
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 12e4;
|
|
5
|
+
function createShopwareClient(connection) {
|
|
6
|
+
return createAdminAPIClient({
|
|
7
|
+
baseURL: `${connection.url.replace(/\/$/, "")}/api`,
|
|
8
|
+
credentials: {
|
|
9
|
+
grant_type: "client_credentials",
|
|
10
|
+
client_id: connection.clientId,
|
|
11
|
+
client_secret: connection.clientSecret
|
|
12
|
+
},
|
|
13
|
+
fetchOptions: { timeout: REQUEST_TIMEOUT_MS }
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/shopware/errors.ts
|
|
18
|
+
var ShopwareConnectionError = class extends Error {};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/shopware/locale.ts
|
|
21
|
+
const SYSTEM_LANGUAGE_ID = "2fbb5fe2e29a4d70aa5854ce7ce3e20b";
|
|
22
|
+
const languageRowSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
locale: z.object({ code: z.string().optional() }).nullish()
|
|
25
|
+
});
|
|
26
|
+
function parseLanguageRows(rows) {
|
|
27
|
+
const result = z.array(languageRowSchema).safeParse(rows);
|
|
28
|
+
if (!result.success) throw new ShopwareConnectionError("Shopware returned an unexpected response shape for languages.");
|
|
29
|
+
return result.data;
|
|
30
|
+
}
|
|
31
|
+
function toShopInfo(rows) {
|
|
32
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33
|
+
const locales = [];
|
|
34
|
+
let systemLocale;
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
const code = row.locale?.code;
|
|
37
|
+
if (!code) continue;
|
|
38
|
+
if (row.id === SYSTEM_LANGUAGE_ID) systemLocale = code;
|
|
39
|
+
if (!seen.has(code)) {
|
|
40
|
+
seen.add(code);
|
|
41
|
+
locales.push(code);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (locales.length === 0) throw new ShopwareConnectionError("Shopware returned no usable locales.");
|
|
45
|
+
return {
|
|
46
|
+
locales,
|
|
47
|
+
defaultLocale: systemLocale ?? locales[0]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/shopware/operations.ts
|
|
52
|
+
function safeJsonParse(input) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(input);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isTimeoutError(error) {
|
|
60
|
+
let current = error;
|
|
61
|
+
while (current instanceof Error) {
|
|
62
|
+
if (current.name === "TimeoutError") return true;
|
|
63
|
+
current = current.cause;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function missingPrivileges(error) {
|
|
68
|
+
for (const e of error.details.errors) {
|
|
69
|
+
if (e.code !== "FRAMEWORK__MISSING_PRIVILEGE_ERROR" || !e.detail) continue;
|
|
70
|
+
const parsed = safeJsonParse(e.detail);
|
|
71
|
+
if (parsed?.missingPrivileges?.length) return parsed.missingPrivileges;
|
|
72
|
+
}
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
function fieldName(pointer) {
|
|
76
|
+
if (!pointer) return null;
|
|
77
|
+
const segments = pointer.split("/").filter((s) => s !== "" && !/^\d+$/.test(s));
|
|
78
|
+
return segments.length ? segments[segments.length - 1] ?? null : null;
|
|
79
|
+
}
|
|
80
|
+
function validationMessages(error) {
|
|
81
|
+
return error.details.errors.map((e) => {
|
|
82
|
+
const field = fieldName(e.source?.pointer);
|
|
83
|
+
const detail = e.detail ?? e.title ?? "Invalid value.";
|
|
84
|
+
return field ? `${field}: ${detail}` : detail;
|
|
85
|
+
}).filter((message, index, all) => all.indexOf(message) === index);
|
|
86
|
+
}
|
|
87
|
+
function toConnectionError(connection, error) {
|
|
88
|
+
if (isTimeoutError(error)) return new ShopwareConnectionError(`${connection.url} did not respond within ${REQUEST_TIMEOUT_MS / 1e3}s, the shop may be slow or unreachable.`);
|
|
89
|
+
if (error instanceof ApiClientError) switch (error.status) {
|
|
90
|
+
case 400: {
|
|
91
|
+
const messages = validationMessages(error);
|
|
92
|
+
if (!messages.length) return new ShopwareConnectionError(`Shopware rejected the request (HTTP 400) from ${connection.url}.`);
|
|
93
|
+
const shown = messages.slice(0, 5);
|
|
94
|
+
const more = messages.length - shown.length;
|
|
95
|
+
return new ShopwareConnectionError(`Shopware rejected the data:\n${shown.map((m) => ` - ${m}`).join("\n")}${more > 0 ? `\n - …and ${more} more` : ""}`);
|
|
96
|
+
}
|
|
97
|
+
case 401: return new ShopwareConnectionError("Authentication failed — check the client ID and client secret of your integration.");
|
|
98
|
+
case 403: {
|
|
99
|
+
const missing = missingPrivileges(error);
|
|
100
|
+
if (missing.length) return new ShopwareConnectionError(`The integration is missing the ${missing.join(", ")} ${missing.length === 1 ? "privilege" : "privileges"} — grant them to its role in Settings → System → Integrations.`);
|
|
101
|
+
return new ShopwareConnectionError("The integration is missing permissions — grant its role admin API access in Settings → System → Integrations.");
|
|
102
|
+
}
|
|
103
|
+
case 404: return new ShopwareConnectionError(`No Shopware admin API found at ${connection.url} — check the shop URL.`);
|
|
104
|
+
default:
|
|
105
|
+
if (error.status >= 500) return new ShopwareConnectionError(`${connection.url} is not responding (HTTP ${error.status}) — the shop may be down or in maintenance.`);
|
|
106
|
+
return new ShopwareConnectionError(`Shopware returned an unexpected response (HTTP ${error.status}) from ${connection.url}.`);
|
|
107
|
+
}
|
|
108
|
+
return new ShopwareConnectionError(`Could not reach ${connection.url} — check the URL and your network connection.`);
|
|
109
|
+
}
|
|
110
|
+
async function validateConnection(connection) {
|
|
111
|
+
const client = createShopwareClient(connection);
|
|
112
|
+
try {
|
|
113
|
+
await client.invoke("infoShopwareVersion get /_info/version");
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw toConnectionError(connection, error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function fetchShopInfo(connection) {
|
|
119
|
+
const client = createShopwareClient(connection);
|
|
120
|
+
try {
|
|
121
|
+
const { data } = await client.invoke("searchLanguage post /search/language", { body: {
|
|
122
|
+
associations: { locale: {} },
|
|
123
|
+
limit: 500
|
|
124
|
+
} });
|
|
125
|
+
return toShopInfo(parseLanguageRows(data.data ?? []));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw toConnectionError(connection, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/shopware/sink.ts
|
|
132
|
+
const SYNC_BATCH_SIZE = 50;
|
|
133
|
+
const ATOMIC_REQUEST_BYTE_LIMIT = 5 * 1024 * 1024;
|
|
134
|
+
function toSyncBody(operations) {
|
|
135
|
+
return operations.map((op) => op.action === "upsert" ? {
|
|
136
|
+
entity: op.entity,
|
|
137
|
+
action: "upsert",
|
|
138
|
+
payload: op.records
|
|
139
|
+
} : {
|
|
140
|
+
entity: op.entity,
|
|
141
|
+
action: "delete",
|
|
142
|
+
payload: op.ids.map((id) => ({ id }))
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function estimateSyncBytes(operations) {
|
|
146
|
+
return Buffer.byteLength(JSON.stringify(toSyncBody(operations)), "utf8");
|
|
147
|
+
}
|
|
148
|
+
function chunk(items, size) {
|
|
149
|
+
const out = [];
|
|
150
|
+
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
function createSyncSink(connection, options = {}) {
|
|
154
|
+
const client = options.client ?? createShopwareClient(connection);
|
|
155
|
+
async function sync(entity, action, payload, onBatch) {
|
|
156
|
+
const batches = chunk(payload, SYNC_BATCH_SIZE);
|
|
157
|
+
let records = 0;
|
|
158
|
+
for (const [i, batch] of batches.entries()) {
|
|
159
|
+
try {
|
|
160
|
+
await client.invoke("sync post /_action/sync", {
|
|
161
|
+
headers: { "indexing-behavior": "use-queue-indexing" },
|
|
162
|
+
body: [{
|
|
163
|
+
entity,
|
|
164
|
+
action,
|
|
165
|
+
payload: batch
|
|
166
|
+
}]
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
throw toConnectionError(connection, error);
|
|
170
|
+
}
|
|
171
|
+
records += batch.length;
|
|
172
|
+
onBatch?.({
|
|
173
|
+
records,
|
|
174
|
+
recordsTotal: payload.length,
|
|
175
|
+
batches: i + 1,
|
|
176
|
+
batchesTotal: batches.length
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
async upsert(entity, records, onBatch) {
|
|
182
|
+
if (records.length > 0) await sync(entity, "upsert", records, onBatch);
|
|
183
|
+
},
|
|
184
|
+
async delete(entity, ids, onBatch) {
|
|
185
|
+
if (ids.length > 0) await sync(entity, "delete", ids.map((id) => ({ id })), onBatch);
|
|
186
|
+
},
|
|
187
|
+
async applyAtomic(operations) {
|
|
188
|
+
const body = toSyncBody(operations).filter((op) => op.payload.length > 0);
|
|
189
|
+
if (body.length === 0) return;
|
|
190
|
+
try {
|
|
191
|
+
await client.invoke("sync post /_action/sync", {
|
|
192
|
+
headers: { "indexing-behavior": "use-queue-indexing" },
|
|
193
|
+
body
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
throw toConnectionError(connection, error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { toConnectionError as a, createShopwareClient as c, fetchShopInfo as i, createSyncSink as n, validateConnection as o, estimateSyncBytes as r, ShopwareConnectionError as s, ATOMIC_REQUEST_BYTE_LIMIT as t };
|
|
203
|
+
|
|
204
|
+
//# sourceMappingURL=shopware-CIZF8Nuo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopware-CIZF8Nuo.mjs","names":[],"sources":["../src/shopware/client.ts","../src/shopware/errors.ts","../src/shopware/locale.ts","../src/shopware/operations.ts","../src/shopware/sink.ts"],"sourcesContent":["import { createAdminAPIClient } from '@shopware/api-client'\nimport type { operations } from '@shopware/api-client/admin-api-types'\nimport type { ShopwareConnection } from './types'\n\nexport type ShopwareClient = ReturnType<typeof createAdminAPIClient<operations>>\n\nexport const REQUEST_TIMEOUT_MS = 120_000\n\nexport function createShopwareClient(connection: ShopwareConnection): ShopwareClient {\n return createAdminAPIClient<operations>({\n baseURL: `${connection.url.replace(/\\/$/, '')}/api`,\n credentials: {\n grant_type: 'client_credentials',\n client_id: connection.clientId,\n client_secret: connection.clientSecret,\n },\n fetchOptions: {\n timeout: REQUEST_TIMEOUT_MS,\n },\n })\n}\n","export class ShopwareConnectionError extends Error {}\n","import { z } from 'zod'\nimport { ShopwareConnectionError } from './errors'\nimport type { ShopInfo } from './types'\n\nconst SYSTEM_LANGUAGE_ID = '2fbb5fe2e29a4d70aa5854ce7ce3e20b'\n\nconst languageRowSchema = z.object({\n id: z.string(),\n locale: z.object({ code: z.string().optional() }).nullish(),\n})\n\nexport type LanguageRow = z.infer<typeof languageRowSchema>\n\nexport function parseLanguageRows(rows: unknown): LanguageRow[] {\n const result = z.array(languageRowSchema).safeParse(rows)\n if (!result.success) {\n throw new ShopwareConnectionError(\n 'Shopware returned an unexpected response shape for languages.',\n )\n }\n return result.data\n}\n\nexport function toShopInfo(rows: LanguageRow[]): ShopInfo {\n const seen = new Set<string>()\n const locales: string[] = []\n let systemLocale: string | undefined\n\n for (const row of rows) {\n const code = row.locale?.code\n if (!code) continue\n if (row.id === SYSTEM_LANGUAGE_ID) systemLocale = code\n if (!seen.has(code)) {\n seen.add(code)\n locales.push(code)\n }\n }\n\n if (locales.length === 0) {\n throw new ShopwareConnectionError('Shopware returned no usable locales.')\n }\n\n return { locales, defaultLocale: systemLocale ?? (locales[0] as string) }\n}\n","import { ApiClientError, type ApiError } from '@shopware/api-client'\nimport { createShopwareClient, REQUEST_TIMEOUT_MS } from './client'\nimport { ShopwareConnectionError } from './errors'\nimport { parseLanguageRows, toShopInfo } from './locale'\nimport type { ShopInfo, ShopwareConnection } from './types'\n\nfunction safeJsonParse<T>(input: string): T | null {\n try {\n return JSON.parse(input) as T\n } catch {\n return null\n }\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n let current: unknown = error\n while (current instanceof Error) {\n if (current.name === 'TimeoutError') return true\n current = current.cause\n }\n return false\n}\n\nfunction missingPrivileges(error: ApiClientError<{ errors: ApiError[] }>): string[] {\n for (const e of error.details.errors) {\n if (e.code !== 'FRAMEWORK__MISSING_PRIVILEGE_ERROR' || !e.detail) continue\n const parsed = safeJsonParse<{ missingPrivileges?: string[] }>(e.detail)\n if (parsed?.missingPrivileges?.length) return parsed.missingPrivileges\n }\n return []\n}\n\nfunction fieldName(pointer: string | undefined): string | null {\n if (!pointer) return null\n const segments = pointer.split('/').filter((s) => s !== '' && !/^\\d+$/.test(s))\n return segments.length ? (segments[segments.length - 1] ?? null) : null\n}\n\nfunction validationMessages(error: ApiClientError<{ errors: ApiError[] }>): string[] {\n return error.details.errors\n .map((e) => {\n const field = fieldName(e.source?.pointer)\n const detail = e.detail ?? e.title ?? 'Invalid value.'\n return field ? `${field}: ${detail}` : detail\n })\n .filter((message, index, all) => all.indexOf(message) === index)\n}\n\nexport function toConnectionError(\n connection: ShopwareConnection,\n error: unknown,\n): ShopwareConnectionError {\n if (isTimeoutError(error)) {\n return new ShopwareConnectionError(\n `${connection.url} did not respond within ${REQUEST_TIMEOUT_MS / 1000}s, the shop may be slow or unreachable.`,\n )\n }\n if (error instanceof ApiClientError) {\n switch (error.status) {\n case 400: {\n const messages = validationMessages(error)\n if (!messages.length) {\n return new ShopwareConnectionError(\n `Shopware rejected the request (HTTP 400) from ${connection.url}.`,\n )\n }\n const shown = messages.slice(0, 5)\n const more = messages.length - shown.length\n const list = shown.map((m) => ` - ${m}`).join('\\n')\n const tail = more > 0 ? `\\n - …and ${more} more` : ''\n return new ShopwareConnectionError(`Shopware rejected the data:\\n${list}${tail}`)\n }\n case 401:\n return new ShopwareConnectionError(\n 'Authentication failed — check the client ID and client secret of your integration.',\n )\n case 403: {\n const missing = missingPrivileges(error)\n if (missing.length) {\n return new ShopwareConnectionError(\n `The integration is missing the ${missing.join(', ')} ${missing.length === 1 ? 'privilege' : 'privileges'} — grant them to its role in Settings → System → Integrations.`,\n )\n }\n return new ShopwareConnectionError(\n 'The integration is missing permissions — grant its role admin API access in Settings → System → Integrations.',\n )\n }\n case 404:\n return new ShopwareConnectionError(\n `No Shopware admin API found at ${connection.url} — check the shop URL.`,\n )\n default:\n if (error.status >= 500) {\n return new ShopwareConnectionError(\n `${connection.url} is not responding (HTTP ${error.status}) — the shop may be down or in maintenance.`,\n )\n }\n return new ShopwareConnectionError(\n `Shopware returned an unexpected response (HTTP ${error.status}) from ${connection.url}.`,\n )\n }\n }\n return new ShopwareConnectionError(\n `Could not reach ${connection.url} — check the URL and your network connection.`,\n )\n}\n\nexport async function validateConnection(connection: ShopwareConnection): Promise<void> {\n const client = createShopwareClient(connection)\n try {\n await client.invoke('infoShopwareVersion get /_info/version')\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n}\n\nexport async function fetchShopInfo(connection: ShopwareConnection): Promise<ShopInfo> {\n const client = createShopwareClient(connection)\n try {\n const { data } = await client.invoke('searchLanguage post /search/language', {\n body: { associations: { locale: {} }, limit: 500 },\n })\n return toShopInfo(parseLanguageRows(data.data ?? []))\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n}\n","import type { OnBatch, ShopwareSink, SinkRecord, SyncOperation } from '../domain'\nimport { createShopwareClient, type ShopwareClient } from './client'\nimport { toConnectionError } from './operations'\nimport type { ShopwareConnection } from './types'\n\nexport interface SyncSinkOptions {\n client?: ShopwareClient\n}\n\nconst SYNC_BATCH_SIZE = 50\n\nexport const ATOMIC_REQUEST_BYTE_LIMIT = 5 * 1024 * 1024\n\ninterface SyncBodyEntry {\n entity: string\n action: 'upsert' | 'delete'\n payload: Record<string, unknown>[]\n}\n\nfunction toSyncBody(operations: SyncOperation[]): SyncBodyEntry[] {\n return operations.map((op) =>\n op.action === 'upsert'\n ? { entity: op.entity, action: 'upsert', payload: op.records }\n : { entity: op.entity, action: 'delete', payload: op.ids.map((id) => ({ id })) },\n )\n}\n\nexport function estimateSyncBytes(operations: SyncOperation[]): number {\n return Buffer.byteLength(JSON.stringify(toSyncBody(operations)), 'utf8')\n}\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = []\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n return out\n}\n\nexport function createSyncSink(\n connection: ShopwareConnection,\n options: SyncSinkOptions = {},\n): ShopwareSink {\n const client = options.client ?? createShopwareClient(connection)\n\n async function sync(\n entity: string,\n action: 'upsert' | 'delete',\n payload: Record<string, unknown>[],\n onBatch?: OnBatch,\n ): Promise<void> {\n const batches = chunk(payload, SYNC_BATCH_SIZE)\n let records = 0\n for (const [i, batch] of batches.entries()) {\n try {\n await client.invoke('sync post /_action/sync', {\n headers: { 'indexing-behavior': 'use-queue-indexing' },\n body: [{ entity, action, payload: batch as never }],\n })\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n records += batch.length\n onBatch?.({\n records,\n recordsTotal: payload.length,\n batches: i + 1,\n batchesTotal: batches.length,\n })\n }\n }\n\n return {\n async upsert(entity: string, records: SinkRecord[], onBatch?: OnBatch): Promise<void> {\n if (records.length > 0) await sync(entity, 'upsert', records, onBatch)\n },\n async delete(entity: string, ids: string[], onBatch?: OnBatch): Promise<void> {\n if (ids.length > 0)\n await sync(\n entity,\n 'delete',\n ids.map((id) => ({ id })),\n onBatch,\n )\n },\n async applyAtomic(operations: SyncOperation[]): Promise<void> {\n const body = toSyncBody(operations).filter((op) => op.payload.length > 0)\n if (body.length === 0) return\n try {\n await client.invoke('sync post /_action/sync', {\n headers: { 'indexing-behavior': 'use-queue-indexing' },\n body: body as never,\n })\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n },\n }\n}\n"],"mappings":";;;AAMA,MAAa,qBAAqB;AAElC,SAAgB,qBAAqB,YAAgD;CACnF,OAAO,qBAAiC;EACtC,SAAS,GAAG,WAAW,IAAI,QAAQ,OAAO,EAAE,EAAE;EAC9C,aAAa;GACX,YAAY;GACZ,WAAW,WAAW;GACtB,eAAe,WAAW;EAC5B;EACA,cAAc,EACZ,SAAS,mBACX;CACF,CAAC;AACH;;;ACpBA,IAAa,0BAAb,cAA6C,MAAM,CAAC;;;ACIpD,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB,EAAE,OAAO;CACjC,IAAI,EAAE,OAAO;CACb,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ;AAC5D,CAAC;AAID,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,SAAS,EAAE,MAAM,iBAAiB,CAAC,CAAC,UAAU,IAAI;CACxD,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,wBACR,+DACF;CAEF,OAAO,OAAO;AAChB;AAEA,SAAgB,WAAW,MAA+B;CACxD,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAAoB,CAAC;CAC3B,IAAI;CAEJ,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,OAAO,IAAI,QAAQ;EACzB,IAAI,CAAC,MAAM;EACX,IAAI,IAAI,OAAO,oBAAoB,eAAe;EAClD,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG;GACnB,KAAK,IAAI,IAAI;GACb,QAAQ,KAAK,IAAI;EACnB;CACF;CAEA,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,wBAAwB,sCAAsC;CAG1E,OAAO;EAAE;EAAS,eAAe,gBAAiB,QAAQ;CAAc;AAC1E;;;ACrCA,SAAS,cAAiB,OAAyB;CACjD,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,UAAmB;CACvB,OAAO,mBAAmB,OAAO;EAC/B,IAAI,QAAQ,SAAS,gBAAgB,OAAO;EAC5C,UAAU,QAAQ;CACpB;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,OAAyD;CAClF,KAAK,MAAM,KAAK,MAAM,QAAQ,QAAQ;EACpC,IAAI,EAAE,SAAS,wCAAwC,CAAC,EAAE,QAAQ;EAClE,MAAM,SAAS,cAAgD,EAAE,MAAM;EACvE,IAAI,QAAQ,mBAAmB,QAAQ,OAAO,OAAO;CACvD;CACA,OAAO,CAAC;AACV;AAEA,SAAS,UAAU,SAA4C;CAC7D,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,WAAW,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,MAAM,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;CAC9E,OAAO,SAAS,SAAU,SAAS,SAAS,SAAS,MAAM,OAAQ;AACrE;AAEA,SAAS,mBAAmB,OAAyD;CACnF,OAAO,MAAM,QAAQ,OAClB,KAAK,MAAM;EACV,MAAM,QAAQ,UAAU,EAAE,QAAQ,OAAO;EACzC,MAAM,SAAS,EAAE,UAAU,EAAE,SAAS;EACtC,OAAO,QAAQ,GAAG,MAAM,IAAI,WAAW;CACzC,CAAC,CAAC,CACD,QAAQ,SAAS,OAAO,QAAQ,IAAI,QAAQ,OAAO,MAAM,KAAK;AACnE;AAEA,SAAgB,kBACd,YACA,OACyB;CACzB,IAAI,eAAe,KAAK,GACtB,OAAO,IAAI,wBACT,GAAG,WAAW,IAAI,0BAA0B,qBAAqB,IAAK,wCACxE;CAEF,IAAI,iBAAiB,gBACnB,QAAQ,MAAM,QAAd;EACE,KAAK,KAAK;GACR,MAAM,WAAW,mBAAmB,KAAK;GACzC,IAAI,CAAC,SAAS,QACZ,OAAO,IAAI,wBACT,iDAAiD,WAAW,IAAI,EAClE;GAEF,MAAM,QAAQ,SAAS,MAAM,GAAG,CAAC;GACjC,MAAM,OAAO,SAAS,SAAS,MAAM;GAGrC,OAAO,IAAI,wBAAwB,gCAFtB,MAAM,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,IAEuB,IADzD,OAAO,IAAI,cAAc,KAAK,SAAS,IAC4B;EAClF;EACA,KAAK,KACH,OAAO,IAAI,wBACT,oFACF;EACF,KAAK,KAAK;GACR,MAAM,UAAU,kBAAkB,KAAK;GACvC,IAAI,QAAQ,QACV,OAAO,IAAI,wBACT,kCAAkC,QAAQ,KAAK,IAAI,EAAE,GAAG,QAAQ,WAAW,IAAI,cAAc,aAAa,+DAC5G;GAEF,OAAO,IAAI,wBACT,+GACF;EACF;EACA,KAAK,KACH,OAAO,IAAI,wBACT,kCAAkC,WAAW,IAAI,uBACnD;EACF;GACE,IAAI,MAAM,UAAU,KAClB,OAAO,IAAI,wBACT,GAAG,WAAW,IAAI,2BAA2B,MAAM,OAAO,4CAC5D;GAEF,OAAO,IAAI,wBACT,kDAAkD,MAAM,OAAO,SAAS,WAAW,IAAI,EACzF;CACJ;CAEF,OAAO,IAAI,wBACT,mBAAmB,WAAW,IAAI,8CACpC;AACF;AAEA,eAAsB,mBAAmB,YAA+C;CACtF,MAAM,SAAS,qBAAqB,UAAU;CAC9C,IAAI;EACF,MAAM,OAAO,OAAO,wCAAwC;CAC9D,SAAS,OAAO;EACd,MAAM,kBAAkB,YAAY,KAAK;CAC3C;AACF;AAEA,eAAsB,cAAc,YAAmD;CACrF,MAAM,SAAS,qBAAqB,UAAU;CAC9C,IAAI;EACF,MAAM,EAAE,SAAS,MAAM,OAAO,OAAO,wCAAwC,EAC3E,MAAM;GAAE,cAAc,EAAE,QAAQ,CAAC,EAAE;GAAG,OAAO;EAAI,EACnD,CAAC;EACD,OAAO,WAAW,kBAAkB,KAAK,QAAQ,CAAC,CAAC,CAAC;CACtD,SAAS,OAAO;EACd,MAAM,kBAAkB,YAAY,KAAK;CAC3C;AACF;;;ACrHA,MAAM,kBAAkB;AAExB,MAAa,4BAA4B,IAAI,OAAO;AAQpD,SAAS,WAAW,YAA8C;CAChE,OAAO,WAAW,KAAK,OACrB,GAAG,WAAW,WACV;EAAE,QAAQ,GAAG;EAAQ,QAAQ;EAAU,SAAS,GAAG;CAAQ,IAC3D;EAAE,QAAQ,GAAG;EAAQ,QAAQ;EAAU,SAAS,GAAG,IAAI,KAAK,QAAQ,EAAE,GAAG,EAAE;CAAE,CACnF;AACF;AAEA,SAAgB,kBAAkB,YAAqC;CACrE,OAAO,OAAO,WAAW,KAAK,UAAU,WAAW,UAAU,CAAC,GAAG,MAAM;AACzE;AAEA,SAAS,MAAS,OAAY,MAAqB;CACjD,MAAM,MAAa,CAAC;CACpB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC;CAC9E,OAAO;AACT;AAEA,SAAgB,eACd,YACA,UAA2B,CAAC,GACd;CACd,MAAM,SAAS,QAAQ,UAAU,qBAAqB,UAAU;CAEhE,eAAe,KACb,QACA,QACA,SACA,SACe;EACf,MAAM,UAAU,MAAM,SAAS,eAAe;EAC9C,IAAI,UAAU;EACd,KAAK,MAAM,CAAC,GAAG,UAAU,QAAQ,QAAQ,GAAG;GAC1C,IAAI;IACF,MAAM,OAAO,OAAO,2BAA2B;KAC7C,SAAS,EAAE,qBAAqB,qBAAqB;KACrD,MAAM,CAAC;MAAE;MAAQ;MAAQ,SAAS;KAAe,CAAC;IACpD,CAAC;GACH,SAAS,OAAO;IACd,MAAM,kBAAkB,YAAY,KAAK;GAC3C;GACA,WAAW,MAAM;GACjB,UAAU;IACR;IACA,cAAc,QAAQ;IACtB,SAAS,IAAI;IACb,cAAc,QAAQ;GACxB,CAAC;EACH;CACF;CAEA,OAAO;EACL,MAAM,OAAO,QAAgB,SAAuB,SAAkC;GACpF,IAAI,QAAQ,SAAS,GAAG,MAAM,KAAK,QAAQ,UAAU,SAAS,OAAO;EACvE;EACA,MAAM,OAAO,QAAgB,KAAe,SAAkC;GAC5E,IAAI,IAAI,SAAS,GACf,MAAM,KACJ,QACA,UACA,IAAI,KAAK,QAAQ,EAAE,GAAG,EAAE,GACxB,OACF;EACJ;EACA,MAAM,YAAY,YAA4C;GAC5D,MAAM,OAAO,WAAW,UAAU,CAAC,CAAC,QAAQ,OAAO,GAAG,QAAQ,SAAS,CAAC;GACxE,IAAI,KAAK,WAAW,GAAG;GACvB,IAAI;IACF,MAAM,OAAO,OAAO,2BAA2B;KAC7C,SAAS,EAAE,qBAAqB,qBAAqB;KAC/C;IACR,CAAC;GACH,SAAS,OAAO;IACd,MAAM,kBAAkB,YAAY,KAAK;GAC3C;EACF;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fakeware/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Fakeware core library that is the base for @fakeware/cli",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"exports": {
|
|
23
|
+
"./package.json": "./package.json",
|
|
23
24
|
".": {
|
|
24
25
|
"types": "./dist/index.d.mts",
|
|
25
26
|
"import": "./dist/index.mjs"
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@shopware/api-client": "1.5.0",
|
|
45
|
+
"jiti": "2.7.0",
|
|
44
46
|
"zod": "4.4.3"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-CSPA4Itj.mjs","names":[],"sources":["../src/runtime/load-module.ts","../src/config/define.ts","../src/config/errors.ts","../src/config/interpolate.ts","../src/config/schema.ts","../src/config/load.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url'\n\nconst RUNTIME_TS_HELP =\n 'fakeware needs to import your TypeScript files at runtime. Run it under Bun, or with Node >=22.6 (native type stripping), or via a TypeScript loader such as tsx.'\n\nexport class LoadModuleError extends Error {}\n\nfunction isBun(): boolean {\n return typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'\n}\n\nfunction nodeStripsTypes(): boolean {\n const feature = (process.features as { typescript?: unknown }).typescript\n return feature === 'strip' || feature === 'transform'\n}\n\nexport async function loadModule<T = unknown>(absPath: string): Promise<T> {\n if (!isBun() && !nodeStripsTypes()) {\n throw new LoadModuleError(RUNTIME_TS_HELP)\n }\n try {\n return (await import(pathToFileURL(absPath).href)) as T\n } catch (error) {\n throw new LoadModuleError(\n `Could not load ${absPath}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n}\n","import type { FakewareUserConfig } from './schema'\n\nexport interface ConfigEnv {\n env: Record<string, string | undefined>\n mode: string\n}\n\nexport type FakewareConfigFn = (env: ConfigEnv) => FakewareUserConfig\n\nexport function defineConfig(config: FakewareUserConfig): FakewareUserConfig\nexport function defineConfig(config: FakewareConfigFn): FakewareConfigFn\nexport function defineConfig(\n config: FakewareUserConfig | FakewareConfigFn,\n): FakewareUserConfig | FakewareConfigFn {\n return config\n}\n","export class ConfigError extends Error {}\n","import { ConfigError } from './errors'\n\nconst ENV_REF = /^\\$([A-Z0-9_]+)$/\n\nexport function interpolate<T>(value: T, env: Record<string, string | undefined>): T {\n if (typeof value === 'string') {\n const match = ENV_REF.exec(value)\n if (!match) return value\n const name = match[1] as string\n const resolved = env[name]\n if (resolved === undefined) {\n throw new ConfigError(`Config references $${name}, but it is not set (check your .env).`)\n }\n return resolved as unknown as T\n }\n if (Array.isArray(value)) {\n return value.map((item) => interpolate(item, env)) as unknown as T\n }\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n out[k] = interpolate(v, env)\n }\n return out as T\n }\n return value\n}\n","import { z } from 'zod'\n\nexport const shopwareSchema = z.object({\n url: z.string().min(1, 'shopware.url is required'),\n clientId: z.string().min(1, 'shopware.clientId is required'),\n clientSecret: z.string().min(1, 'shopware.clientSecret is required'),\n})\n\nexport const fakewareConfigSchema = z.object({\n shopware: shopwareSchema.optional(),\n})\n\nexport type FakewareConfig = z.output<typeof fakewareConfigSchema>\n\nexport type FakewareUserConfig = z.input<typeof fakewareConfigSchema>\n","import { access, readFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { loadModule } from '../runtime'\nimport type { ShopwareConnection } from '../shopware'\nimport type { ConfigEnv, FakewareConfigFn } from './define'\nimport { ConfigError } from './errors'\nimport { interpolate } from './interpolate'\nimport { type FakewareConfig, type FakewareUserConfig, fakewareConfigSchema } from './schema'\n\nexport const DEFAULT_CONFIG_FILENAME = 'fakeware.config.ts'\n\nexport interface LoadConfigOptions {\n cwd?: string\n configFile?: string\n mode?: string\n}\n\nexport interface LoadedConfig {\n config: FakewareConfig\n connection: ShopwareConnection\n configPath: string\n projectRoot: string\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path)\n return true\n } catch {\n return false\n }\n}\n\nasync function findConfig(cwd: string): Promise<string> {\n let dir = cwd\n for (;;) {\n const candidate = join(dir, DEFAULT_CONFIG_FILENAME)\n if (await fileExists(candidate)) return candidate\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n throw new ConfigError(\n `No ${DEFAULT_CONFIG_FILENAME} found in ${cwd} or any parent directory. Run \\`fakeware init\\` first.`,\n )\n}\n\nasync function readEnvFile(projectRoot: string): Promise<Record<string, string>> {\n const path = join(projectRoot, '.env')\n if (!(await fileExists(path))) return {}\n const out: Record<string, string> = {}\n const contents = await readFile(path, 'utf8')\n for (const raw of contents.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n const eq = line.indexOf('=')\n if (eq === -1) continue\n const key = line.slice(0, eq).trim()\n let val = line.slice(eq + 1).trim()\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1)\n }\n out[key] = val\n }\n return out\n}\n\nfunction isConfigFn(value: unknown): value is FakewareConfigFn {\n return typeof value === 'function'\n}\n\nexport async function loadConfig(opts: LoadConfigOptions = {}): Promise<LoadedConfig> {\n const cwd = opts.cwd ?? process.cwd()\n const configPath = opts.configFile\n ? isAbsolute(opts.configFile)\n ? opts.configFile\n : resolve(cwd, opts.configFile)\n : await findConfig(cwd)\n const projectRoot = dirname(configPath)\n\n const env: Record<string, string | undefined> = {\n ...process.env,\n ...(await readEnvFile(projectRoot)),\n }\n\n const mod = await loadModule<{ default?: unknown }>(configPath)\n const exported = mod.default\n if (exported === undefined) {\n throw new ConfigError(`${configPath} must \\`export default defineConfig(...)\\`.`)\n }\n\n const configEnv: ConfigEnv = { env, mode: opts.mode ?? 'development' }\n const raw = isConfigFn(exported) ? exported(configEnv) : (exported as FakewareUserConfig)\n\n const interpolated = interpolate(raw, env)\n\n const parsed = fakewareConfigSchema.safeParse(interpolated)\n if (!parsed.success) {\n throw new ConfigError(`Invalid config in ${configPath}: ${parsed.error.message}`)\n }\n\n const { shopware } = parsed.data\n if (!shopware) {\n throw new ConfigError(\n `No \\`shopware\\` connection configured in ${configPath}. up/down need a shop to talk to.`,\n )\n }\n\n return { config: parsed.data, connection: shopware, configPath, projectRoot }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/shopware/client.ts","../../src/shopware/errors.ts","../../src/shopware/locale.ts","../../src/shopware/operations.ts","../../src/shopware/sink.ts"],"sourcesContent":["import { createAdminAPIClient } from '@shopware/api-client'\nimport type { operations } from '@shopware/api-client/admin-api-types'\nimport type { ShopwareConnection } from './types'\n\nexport type ShopwareClient = ReturnType<typeof createAdminAPIClient<operations>>\n\nexport const REQUEST_TIMEOUT_MS = 120_000\n\nexport function createShopwareClient(connection: ShopwareConnection): ShopwareClient {\n return createAdminAPIClient<operations>({\n baseURL: `${connection.url.replace(/\\/$/, '')}/api`,\n credentials: {\n grant_type: 'client_credentials',\n client_id: connection.clientId,\n client_secret: connection.clientSecret,\n },\n fetchOptions: {\n timeout: REQUEST_TIMEOUT_MS,\n },\n })\n}\n","export class ShopwareConnectionError extends Error {}\n","import { z } from 'zod'\nimport { ShopwareConnectionError } from './errors'\nimport type { ShopInfo } from './types'\n\nconst SYSTEM_LANGUAGE_ID = '2fbb5fe2e29a4d70aa5854ce7ce3e20b'\n\nconst languageRowSchema = z.object({\n id: z.string(),\n locale: z.object({ code: z.string().optional() }).nullish(),\n})\n\nexport type LanguageRow = z.infer<typeof languageRowSchema>\n\nexport function parseLanguageRows(rows: unknown): LanguageRow[] {\n const result = z.array(languageRowSchema).safeParse(rows)\n if (!result.success) {\n throw new ShopwareConnectionError(\n 'Shopware returned an unexpected response shape for languages.',\n )\n }\n return result.data\n}\n\nexport function toShopInfo(rows: LanguageRow[]): ShopInfo {\n const seen = new Set<string>()\n const locales: string[] = []\n let systemLocale: string | undefined\n\n for (const row of rows) {\n const code = row.locale?.code\n if (!code) continue\n if (row.id === SYSTEM_LANGUAGE_ID) systemLocale = code\n if (!seen.has(code)) {\n seen.add(code)\n locales.push(code)\n }\n }\n\n if (locales.length === 0) {\n throw new ShopwareConnectionError('Shopware returned no usable locales.')\n }\n\n return { locales, defaultLocale: systemLocale ?? (locales[0] as string) }\n}\n","import { ApiClientError, type ApiError } from '@shopware/api-client'\nimport { createShopwareClient, REQUEST_TIMEOUT_MS } from './client'\nimport { ShopwareConnectionError } from './errors'\nimport { parseLanguageRows, toShopInfo } from './locale'\nimport type { ShopInfo, ShopwareConnection } from './types'\n\nfunction safeJsonParse<T>(input: string): T | null {\n try {\n return JSON.parse(input) as T\n } catch {\n return null\n }\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n let current: unknown = error\n while (current instanceof Error) {\n if (current.name === 'TimeoutError') return true\n current = current.cause\n }\n return false\n}\n\nfunction missingPrivileges(error: ApiClientError<{ errors: ApiError[] }>): string[] {\n for (const e of error.details.errors) {\n if (e.code !== 'FRAMEWORK__MISSING_PRIVILEGE_ERROR' || !e.detail) continue\n const parsed = safeJsonParse<{ missingPrivileges?: string[] }>(e.detail)\n if (parsed?.missingPrivileges?.length) return parsed.missingPrivileges\n }\n return []\n}\n\nfunction validationMessages(error: ApiClientError<{ errors: ApiError[] }>): string[] {\n return error.details.errors\n .map((e) => {\n const field = e.source?.pointer?.replace(/^\\/\\d+\\/\\d+\\//, '')\n const detail = e.detail ?? e.title ?? 'Invalid value.'\n return field ? `${field}: ${detail}` : detail\n })\n .filter((message, index, all) => all.indexOf(message) === index)\n}\n\nexport function toConnectionError(\n connection: ShopwareConnection,\n error: unknown,\n): ShopwareConnectionError {\n if (isTimeoutError(error)) {\n return new ShopwareConnectionError(\n `${connection.url} did not respond within ${REQUEST_TIMEOUT_MS / 1000}s, the shop may be slow or unreachable.`,\n )\n }\n if (error instanceof ApiClientError) {\n switch (error.status) {\n case 400: {\n const messages = validationMessages(error)\n return new ShopwareConnectionError(\n messages.length\n ? `Shopware rejected the data — ${messages.join(' ')}`\n : `Shopware rejected the request (HTTP 400) from ${connection.url}.`,\n )\n }\n case 401:\n return new ShopwareConnectionError(\n 'Authentication failed — check the client ID and client secret of your integration.',\n )\n case 403: {\n const missing = missingPrivileges(error)\n if (missing.length) {\n return new ShopwareConnectionError(\n `The integration is missing the ${missing.join(', ')} ${missing.length === 1 ? 'privilege' : 'privileges'} — grant them to its role in Settings → System → Integrations.`,\n )\n }\n return new ShopwareConnectionError(\n 'The integration is missing permissions — grant its role admin API access in Settings → System → Integrations.',\n )\n }\n case 404:\n return new ShopwareConnectionError(\n `No Shopware admin API found at ${connection.url} — check the shop URL.`,\n )\n default:\n if (error.status >= 500) {\n return new ShopwareConnectionError(\n `${connection.url} is not responding (HTTP ${error.status}) — the shop may be down or in maintenance.`,\n )\n }\n return new ShopwareConnectionError(\n `Shopware returned an unexpected response (HTTP ${error.status}) from ${connection.url}.`,\n )\n }\n }\n return new ShopwareConnectionError(\n `Could not reach ${connection.url} — check the URL and your network connection.`,\n )\n}\n\nexport async function validateConnection(connection: ShopwareConnection): Promise<void> {\n const client = createShopwareClient(connection)\n try {\n await client.invoke('infoShopwareVersion get /_info/version')\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n}\n\nexport async function fetchShopInfo(connection: ShopwareConnection): Promise<ShopInfo> {\n const client = createShopwareClient(connection)\n try {\n const { data } = await client.invoke('searchLanguage post /search/language', {\n body: { associations: { locale: {} }, limit: 500 },\n })\n return toShopInfo(parseLanguageRows(data.data ?? []))\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n}\n","import type { ShopwareSink, SinkRecord } from '../domain'\nimport { createShopwareClient, type ShopwareClient } from './client'\nimport { toConnectionError } from './operations'\nimport type { ShopwareConnection } from './types'\n\nexport interface SyncSinkOptions {\n client?: ShopwareClient\n}\n\nconst SYNC_BATCH_SIZE = 50\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = []\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))\n return out\n}\n\nexport function createSyncSink(\n connection: ShopwareConnection,\n options: SyncSinkOptions = {},\n): ShopwareSink {\n const client = options.client ?? createShopwareClient(connection)\n\n async function sync(\n entity: string,\n action: 'upsert' | 'delete',\n payload: Record<string, unknown>[],\n ): Promise<void> {\n for (const batch of chunk(payload, SYNC_BATCH_SIZE)) {\n try {\n await client.invoke('sync post /_action/sync', {\n headers: { 'indexing-behavior': 'use-queue-indexing' },\n body: [{ entity, action, payload: batch as never }],\n })\n } catch (error) {\n throw toConnectionError(connection, error)\n }\n }\n }\n\n return {\n async upsert(entity: string, records: SinkRecord[]): Promise<void> {\n if (records.length > 0) await sync(entity, 'upsert', records)\n },\n async delete(entity: string, ids: string[]): Promise<void> {\n if (ids.length > 0)\n await sync(\n entity,\n 'delete',\n ids.map((id) => ({ id })),\n )\n },\n }\n}\n"],"mappings":";;;AAMA,MAAa,qBAAqB;AAElC,SAAgB,qBAAqB,YAAgD;CACnF,OAAO,qBAAiC;EACtC,SAAS,GAAG,WAAW,IAAI,QAAQ,OAAO,EAAE,EAAE;EAC9C,aAAa;GACX,YAAY;GACZ,WAAW,WAAW;GACtB,eAAe,WAAW;EAC5B;EACA,cAAc,EACZ,SAAS,mBACX;CACF,CAAC;AACH;;;ACpBA,IAAa,0BAAb,cAA6C,MAAM,CAAC;;;ACIpD,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB,EAAE,OAAO;CACjC,IAAI,EAAE,OAAO;CACb,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ;AAC5D,CAAC;AAID,SAAgB,kBAAkB,MAA8B;CAC9D,MAAM,SAAS,EAAE,MAAM,iBAAiB,EAAE,UAAU,IAAI;CACxD,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,wBACR,+DACF;CAEF,OAAO,OAAO;AAChB;AAEA,SAAgB,WAAW,MAA+B;CACxD,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAAoB,CAAC;CAC3B,IAAI;CAEJ,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,OAAO,IAAI,QAAQ;EACzB,IAAI,CAAC,MAAM;EACX,IAAI,IAAI,OAAO,oBAAoB,eAAe;EAClD,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG;GACnB,KAAK,IAAI,IAAI;GACb,QAAQ,KAAK,IAAI;EACnB;CACF;CAEA,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,wBAAwB,sCAAsC;CAG1E,OAAO;EAAE;EAAS,eAAe,gBAAiB,QAAQ;CAAc;AAC1E;;;ACrCA,SAAS,cAAiB,OAAyB;CACjD,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,UAAmB;CACvB,OAAO,mBAAmB,OAAO;EAC/B,IAAI,QAAQ,SAAS,gBAAgB,OAAO;EAC5C,UAAU,QAAQ;CACpB;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,OAAyD;CAClF,KAAK,MAAM,KAAK,MAAM,QAAQ,QAAQ;EACpC,IAAI,EAAE,SAAS,wCAAwC,CAAC,EAAE,QAAQ;EAClE,MAAM,SAAS,cAAgD,EAAE,MAAM;EACvE,IAAI,QAAQ,mBAAmB,QAAQ,OAAO,OAAO;CACvD;CACA,OAAO,CAAC;AACV;AAEA,SAAS,mBAAmB,OAAyD;CACnF,OAAO,MAAM,QAAQ,OAClB,KAAK,MAAM;EACV,MAAM,QAAQ,EAAE,QAAQ,SAAS,QAAQ,iBAAiB,EAAE;EAC5D,MAAM,SAAS,EAAE,UAAU,EAAE,SAAS;EACtC,OAAO,QAAQ,GAAG,MAAM,IAAI,WAAW;CACzC,CAAC,EACA,QAAQ,SAAS,OAAO,QAAQ,IAAI,QAAQ,OAAO,MAAM,KAAK;AACnE;AAEA,SAAgB,kBACd,YACA,OACyB;CACzB,IAAI,eAAe,KAAK,GACtB,OAAO,IAAI,wBACT,GAAG,WAAW,IAAI,0BAA0B,qBAAqB,IAAK,wCACxE;CAEF,IAAI,iBAAiB,gBACnB,QAAQ,MAAM,QAAd;EACE,KAAK,KAAK;GACR,MAAM,WAAW,mBAAmB,KAAK;GACzC,OAAO,IAAI,wBACT,SAAS,SACL,gCAAgC,SAAS,KAAK,GAAG,MACjD,iDAAiD,WAAW,IAAI,EACtE;EACF;EACA,KAAK,KACH,OAAO,IAAI,wBACT,oFACF;EACF,KAAK,KAAK;GACR,MAAM,UAAU,kBAAkB,KAAK;GACvC,IAAI,QAAQ,QACV,OAAO,IAAI,wBACT,kCAAkC,QAAQ,KAAK,IAAI,EAAE,GAAG,QAAQ,WAAW,IAAI,cAAc,aAAa,+DAC5G;GAEF,OAAO,IAAI,wBACT,+GACF;EACF;EACA,KAAK,KACH,OAAO,IAAI,wBACT,kCAAkC,WAAW,IAAI,uBACnD;EACF;GACE,IAAI,MAAM,UAAU,KAClB,OAAO,IAAI,wBACT,GAAG,WAAW,IAAI,2BAA2B,MAAM,OAAO,4CAC5D;GAEF,OAAO,IAAI,wBACT,kDAAkD,MAAM,OAAO,SAAS,WAAW,IAAI,EACzF;CACJ;CAEF,OAAO,IAAI,wBACT,mBAAmB,WAAW,IAAI,8CACpC;AACF;AAEA,eAAsB,mBAAmB,YAA+C;CACtF,MAAM,SAAS,qBAAqB,UAAU;CAC9C,IAAI;EACF,MAAM,OAAO,OAAO,wCAAwC;CAC9D,SAAS,OAAO;EACd,MAAM,kBAAkB,YAAY,KAAK;CAC3C;AACF;AAEA,eAAsB,cAAc,YAAmD;CACrF,MAAM,SAAS,qBAAqB,UAAU;CAC9C,IAAI;EACF,MAAM,EAAE,SAAS,MAAM,OAAO,OAAO,wCAAwC,EAC3E,MAAM;GAAE,cAAc,EAAE,QAAQ,CAAC,EAAE;GAAG,OAAO;EAAI,EACnD,CAAC;EACD,OAAO,WAAW,kBAAkB,KAAK,QAAQ,CAAC,CAAC,CAAC;CACtD,SAAS,OAAO;EACd,MAAM,kBAAkB,YAAY,KAAK;CAC3C;AACF;;;AC1GA,MAAM,kBAAkB;AAExB,SAAS,MAAS,OAAY,MAAqB;CACjD,MAAM,MAAa,CAAC;CACpB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC;CAC9E,OAAO;AACT;AAEA,SAAgB,eACd,YACA,UAA2B,CAAC,GACd;CACd,MAAM,SAAS,QAAQ,UAAU,qBAAqB,UAAU;CAEhE,eAAe,KACb,QACA,QACA,SACe;EACf,KAAK,MAAM,SAAS,MAAM,SAAS,eAAe,GAChD,IAAI;GACF,MAAM,OAAO,OAAO,2BAA2B;IAC7C,SAAS,EAAE,qBAAqB,qBAAqB;IACrD,MAAM,CAAC;KAAE;KAAQ;KAAQ,SAAS;IAAe,CAAC;GACpD,CAAC;EACH,SAAS,OAAO;GACd,MAAM,kBAAkB,YAAY,KAAK;EAC3C;CAEJ;CAEA,OAAO;EACL,MAAM,OAAO,QAAgB,SAAsC;GACjE,IAAI,QAAQ,SAAS,GAAG,MAAM,KAAK,QAAQ,UAAU,OAAO;EAC9D;EACA,MAAM,OAAO,QAAgB,KAA8B;GACzD,IAAI,IAAI,SAAS,GACf,MAAM,KACJ,QACA,UACA,IAAI,KAAK,QAAQ,EAAE,GAAG,EAAE,CAC1B;EACJ;CACF;AACF"}
|