@adobe/aio-commerce-lib-app 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/cjs/actions/index.cjs +824 -0
  3. package/dist/cjs/actions/index.d.cts +18 -0
  4. package/dist/cjs/app-Dx0ca6oL.d.cts +181 -0
  5. package/dist/cjs/commands/generate/actions/templates/app-management/custom-scripts.js.template +28 -0
  6. package/dist/cjs/commands/generate/actions/templates/app-management/get-app-config.js.template +62 -0
  7. package/dist/cjs/commands/generate/actions/templates/app-management/installation.js.template +21 -0
  8. package/dist/cjs/commands/generate/actions/templates/{get-app-config.js.template → business-configuration/get-config-schema.js.template} +13 -11
  9. package/dist/cjs/commands/generate/actions/templates/business-configuration/get-configuration.js.template +104 -0
  10. package/dist/cjs/commands/generate/actions/templates/business-configuration/get-scope-tree.js.template +69 -0
  11. package/dist/cjs/commands/generate/actions/templates/business-configuration/set-configuration.js.template +125 -0
  12. package/dist/cjs/commands/generate/actions/templates/business-configuration/set-custom-scope-tree.js.template +83 -0
  13. package/dist/cjs/commands/generate/actions/templates/business-configuration/sync-commerce-scopes.js.template +113 -0
  14. package/dist/cjs/commands/generate/actions/templates/business-configuration/unsync-commerce-scopes.js.template +56 -0
  15. package/dist/cjs/commands/index.cjs +1075 -117
  16. package/dist/cjs/config/index.cjs +20 -1
  17. package/dist/cjs/config/index.d.cts +591 -364
  18. package/dist/cjs/config-JQ_n-5Nk.cjs +565 -0
  19. package/dist/cjs/error-Byj1DVHZ.cjs +344 -0
  20. package/dist/cjs/index-C5SutkJQ.d.cts +345 -0
  21. package/dist/cjs/logging-DYwr5WQk.cjs +25 -0
  22. package/dist/cjs/management/index.cjs +9 -0
  23. package/dist/cjs/management/index.d.cts +2 -0
  24. package/dist/cjs/management-Dm5h0E6l.cjs +1246 -0
  25. package/dist/es/actions/index.d.mts +18 -0
  26. package/dist/es/actions/index.mjs +822 -0
  27. package/dist/es/app-Cx1-6dn0.d.mts +181 -0
  28. package/dist/es/commands/generate/actions/templates/app-management/custom-scripts.js.template +28 -0
  29. package/dist/es/commands/generate/actions/templates/app-management/get-app-config.js.template +62 -0
  30. package/dist/es/commands/generate/actions/templates/app-management/installation.js.template +21 -0
  31. package/dist/es/commands/generate/actions/templates/{get-app-config.js.template → business-configuration/get-config-schema.js.template} +13 -11
  32. package/dist/es/commands/generate/actions/templates/business-configuration/get-configuration.js.template +104 -0
  33. package/dist/es/commands/generate/actions/templates/business-configuration/get-scope-tree.js.template +69 -0
  34. package/dist/es/commands/generate/actions/templates/business-configuration/set-configuration.js.template +125 -0
  35. package/dist/es/commands/generate/actions/templates/business-configuration/set-custom-scope-tree.js.template +83 -0
  36. package/dist/es/commands/generate/actions/templates/business-configuration/sync-commerce-scopes.js.template +113 -0
  37. package/dist/es/commands/generate/actions/templates/business-configuration/unsync-commerce-scopes.js.template +56 -0
  38. package/dist/es/commands/index.mjs +1070 -117
  39. package/dist/es/config/index.d.mts +591 -364
  40. package/dist/es/config/index.mjs +4 -1
  41. package/dist/es/config-BSGerqCG.mjs +457 -0
  42. package/dist/es/error-P7JgUTds.mjs +251 -0
  43. package/dist/es/index-Bxr3zvCT.d.mts +345 -0
  44. package/dist/es/logging-VgerMhp6.mjs +18 -0
  45. package/dist/es/management/index.d.mts +3 -0
  46. package/dist/es/management/index.mjs +3 -0
  47. package/dist/es/management-Y7pwEbNI.mjs +1204 -0
  48. package/package.json +46 -6
  49. package/dist/cjs/parser-x_oUOtEM.cjs +0 -1
  50. package/dist/es/parser-djQHmDCQ.mjs +0 -1
@@ -0,0 +1,822 @@
1
+ import { u as nonEmptyStringValueSchema } from "../error-P7JgUTds.mjs";
2
+ import { a as isFailedState, i as isCompletedState, n as runInstallation, o as isInProgressState, s as isSucceededState, t as createInitialInstallationState } from "../management-Y7pwEbNI.mjs";
3
+ import { accepted, badRequest, conflict, internalServerError, methodNotAllowed, noContent, notFound, ok } from "@adobe/aio-commerce-lib-core/responses";
4
+ import AioLogger from "@adobe/aio-lib-core-logging";
5
+ import { parse } from "regexparam";
6
+ import { init } from "@adobe/aio-lib-files";
7
+ import { init as init$1 } from "@adobe/aio-lib-state";
8
+ import openwhisk from "openwhisk";
9
+ import * as v from "valibot";
10
+ import { object, string } from "valibot";
11
+
12
+ //#region ../../packages-private/common-utils/source/actions/http/middleware/logger.ts
13
+ /**
14
+ * Creates a logger middleware that adds logging capabilities to the context.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * router.use(logger({ level: "debug", name: () => "my-logger-name" }));
19
+ *
20
+ * router.get("/test", {
21
+ * handler: (req, ctx) => {
22
+ * ctx.logger.info("Hello world");
23
+ * return ok({ body: {} });
24
+ * },
25
+ * });
26
+ * ```
27
+ */
28
+ function logger({ name, ...restOptions } = {}) {
29
+ return (ctx) => {
30
+ const params = ctx.rawParams;
31
+ return { logger: AioLogger(`${params.__ow_method}-${name?.(ctx) ?? process.env.__OW_ACTION_NAME}`, {
32
+ level: `${params.LOG_LEVEL ?? "info"}`,
33
+ ...restOptions
34
+ }) };
35
+ };
36
+ }
37
+
38
+ //#endregion
39
+ //#region ../../packages-private/common-utils/source/actions/http/utils.ts
40
+ /**
41
+ * Validates input against a Standard Schema and returns a result.
42
+ *
43
+ * @template TInput - The input type expected by the schema
44
+ * @template TOutput - The output type produced by the schema
45
+ * @param schema - A Standard Schema v1 compliant schema
46
+ * @param input - The input data to validate
47
+ * @returns A promise resolving to either success with validated data or failure with issues
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const result = await validateSchema(mySchema, userInput);
52
+ * if (result.success) {
53
+ * console.log(result.data); // Typed as TOutput
54
+ * } else {
55
+ * console.error(result.issues); // Validation errors
56
+ * }
57
+ * ```
58
+ */
59
+ async function validateSchema(schema, input) {
60
+ const result = await schema["~standard"].validate(input);
61
+ if (result.issues) return {
62
+ success: false,
63
+ issues: result.issues.map((issue) => ({
64
+ message: issue.message,
65
+ path: issue.path?.map((segment) => typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment)
66
+ }))
67
+ };
68
+ return {
69
+ success: true,
70
+ data: result.value
71
+ };
72
+ }
73
+ /**
74
+ * Parses a request body from OpenWhisk/Runtime.
75
+ * Handles multiple formats:
76
+ * - Base64-encoded strings (__ow_body)
77
+ * - Already-parsed objects
78
+ * - Body properties mixed into args (web actions with JSON content-type)
79
+ *
80
+ * @param owBody - Body from __ow_body (base64 string, JSON string, or object)
81
+ * @param args - Full args object to extract body from if __ow_body is not present
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const body = parseRequestBody(params.__ow_body, params);
86
+ * ```
87
+ */
88
+ function parseRequestBody(owBody, args) {
89
+ if (owBody) {
90
+ if (typeof owBody === "object") return owBody;
91
+ if (typeof owBody === "string") {
92
+ try {
93
+ return JSON.parse(owBody);
94
+ } catch {}
95
+ try {
96
+ const decoded = Buffer.from(owBody, "base64").toString();
97
+ return JSON.parse(decoded);
98
+ } catch {}
99
+ }
100
+ }
101
+ if (args && typeof args === "object") {
102
+ const body = {};
103
+ for (const [key, value] of Object.entries(args)) if (!key.startsWith("__ow_")) body[key] = value;
104
+ return body;
105
+ }
106
+ return {};
107
+ }
108
+ /**
109
+ * Parses query parameters from OpenWhisk/Runtime format.
110
+ *
111
+ * @param queryString - Query string from __ow_query
112
+ * @param fallbackParams - Fallback params object (used when __ow_query is not present)
113
+ * @returns Parsed query parameters as a record
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const query = parseQueryParams(params.__ow_query, params);
118
+ * ```
119
+ */
120
+ function parseQueryParams(queryString, fallbackParams) {
121
+ if (queryString) return Object.fromEntries(new URLSearchParams(queryString));
122
+ if (fallbackParams) {
123
+ const { __ow_method, __ow_path, __ow_headers, __ow_body, __ow_query, ...rest } = fallbackParams;
124
+ return rest;
125
+ }
126
+ return {};
127
+ }
128
+
129
+ //#endregion
130
+ //#region ../../packages-private/common-utils/source/actions/http/router.ts
131
+ /**
132
+ * HTTP router for Adobe I/O Runtime actions.
133
+ * Provides type-safe routing with schema validation and OpenWhisk integration.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const router = new HttpActionRouter();
138
+ *
139
+ * router.get("/users/:id", {
140
+ * handler: (req) => ok({ id: req.params.id, context: req.context })
141
+ * });
142
+ *
143
+ * // Add context builders
144
+ * router.use(async (base) => ({
145
+ * user: await getUser(base.rawParams.__ow_headers?.authorization),
146
+ * }));
147
+ *
148
+ * export const main = router.handler();
149
+ * ```
150
+ */
151
+ var HttpActionRouter = class {
152
+ constructor() {
153
+ this.routes = [];
154
+ this.contextBuilders = [];
155
+ }
156
+ /**
157
+ * Internal method to add a route to the router.
158
+ */
159
+ addRoute(method, path, config) {
160
+ const { pattern, keys } = parse(path);
161
+ this.routes.push({
162
+ method,
163
+ pattern,
164
+ keys,
165
+ params: config.params,
166
+ body: config.body,
167
+ query: config.query,
168
+ handler: config.handler
169
+ });
170
+ return this;
171
+ }
172
+ /**
173
+ * Register a GET route.
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * router.get("/users/:id", {
178
+ * handler: (req) => ok({ id: req.params.id })
179
+ * });
180
+ * ```
181
+ */
182
+ get(path, config) {
183
+ return this.addRoute("GET", path, config);
184
+ }
185
+ /**
186
+ * Register a POST route.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * router.post("/users", {
191
+ * body: userSchema,
192
+ * handler: (req) => created(req.body)
193
+ * });
194
+ * ```
195
+ */
196
+ post(path, config) {
197
+ return this.addRoute("POST", path, config);
198
+ }
199
+ /**
200
+ * Register a PUT route.
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * router.put("/users/:id", {
205
+ * body: userSchema,
206
+ * handler: (req) => ok(req.body)
207
+ * });
208
+ * ```
209
+ */
210
+ put(path, config) {
211
+ return this.addRoute("PUT", path, config);
212
+ }
213
+ /**
214
+ * Register a PATCH route.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * router.patch("/users/:id", {
219
+ * body: partialUserSchema,
220
+ * handler: (req) => ok(req.body)
221
+ * });
222
+ * ```
223
+ */
224
+ patch(path, config) {
225
+ return this.addRoute("PATCH", path, config);
226
+ }
227
+ /**
228
+ * Register a DELETE route.
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * router.delete("/users/:id", {
233
+ * handler: (req) => noContent()
234
+ * });
235
+ * ```
236
+ */
237
+ delete(path, config) {
238
+ return this.addRoute("DELETE", path, config);
239
+ }
240
+ /**
241
+ * Register a context builder that runs before route handlers.
242
+ * Context builders can add properties to the request context.
243
+ * Multiple builders are executed in order and their results are merged.
244
+ *
245
+ * The returned router has an updated context type that includes the new properties,
246
+ * enabling type-safe access in route handlers.
247
+ *
248
+ * @param builder - Function that receives base context and returns additional context
249
+ * @returns The router instance with updated context type for chaining
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const router = new HttpActionRouter()
254
+ * .use(logger()) // HttpActionRouter<BaseContext & { logger: Logger }>
255
+ * .use(auth()); // HttpActionRouter<BaseContext & { logger: Logger } & { user: User }>
256
+ *
257
+ * router.get("/me", {
258
+ * handler: (req, ctx) => {
259
+ * ctx.logger.info("Hello"); // ✅ typed
260
+ * return ok({ body: ctx.user }); // ✅ typed
261
+ * },
262
+ * });
263
+ * ```
264
+ */
265
+ use(builder) {
266
+ this.contextBuilders.push(builder);
267
+ return this;
268
+ }
269
+ /**
270
+ * Builds the full context by running all context builders.
271
+ */
272
+ async buildContext(args) {
273
+ let context = { rawParams: args };
274
+ for (const builder of this.contextBuilders) {
275
+ const result = await builder(context);
276
+ if (result) context = {
277
+ ...context,
278
+ ...result
279
+ };
280
+ }
281
+ return context;
282
+ }
283
+ /**
284
+ * Validates and extracts route parameters.
285
+ */
286
+ async validateParams(route, params) {
287
+ if (!route.params) return {
288
+ success: true,
289
+ data: params
290
+ };
291
+ const result = await validateSchema(route.params, params);
292
+ if (!result.success) return {
293
+ success: false,
294
+ issues: result.issues
295
+ };
296
+ return {
297
+ success: true,
298
+ data: result.data
299
+ };
300
+ }
301
+ /**
302
+ * Validates request body.
303
+ */
304
+ async validateBody(route, body) {
305
+ if (!route.body) return {
306
+ success: true,
307
+ data: body
308
+ };
309
+ const result = await validateSchema(route.body, body);
310
+ if (!result.success) return {
311
+ success: false,
312
+ issues: result.issues
313
+ };
314
+ return {
315
+ success: true,
316
+ data: result.data
317
+ };
318
+ }
319
+ /**
320
+ * Validates query parameters.
321
+ */
322
+ async validateQuery(route, query) {
323
+ if (!route.query) return {
324
+ success: true,
325
+ data: query
326
+ };
327
+ const result = await validateSchema(route.query, query);
328
+ if (!result.success) return {
329
+ success: false,
330
+ issues: result.issues
331
+ };
332
+ return {
333
+ success: true,
334
+ data: result.data
335
+ };
336
+ }
337
+ /** Handles a matched route by validating inputs and calling the handler. */
338
+ async handleRoute(route, match, body, query, headers, method, path, context) {
339
+ const params = {};
340
+ route.keys.forEach((key, i) => {
341
+ params[key] = decodeURIComponent(match[i + 1] || "");
342
+ });
343
+ const paramsResult = await this.validateParams(route, params);
344
+ if (!paramsResult.success) return badRequest({ body: {
345
+ message: "Invalid route parameters",
346
+ issues: paramsResult.issues
347
+ } });
348
+ const bodyResult = await this.validateBody(route, body);
349
+ if (!bodyResult.success) return badRequest({ body: {
350
+ message: "Invalid request body",
351
+ issues: bodyResult.issues
352
+ } });
353
+ const queryResult = await this.validateQuery(route, query);
354
+ if (!queryResult.success) return badRequest({ body: {
355
+ message: "Invalid query parameters",
356
+ issues: queryResult.issues
357
+ } });
358
+ try {
359
+ return await route.handler({
360
+ params: paramsResult.data,
361
+ body: bodyResult.data,
362
+ query: queryResult.data,
363
+ headers,
364
+ method,
365
+ path
366
+ }, context);
367
+ } catch (err) {
368
+ console.error("Handler error:", err);
369
+ return internalServerError({ body: {
370
+ message: "Internal server error",
371
+ error: err instanceof Error ? err.message : "Unknown error"
372
+ } });
373
+ }
374
+ }
375
+ /**
376
+ * Creates an OpenWhisk/Runtime action handler from the registered routes.
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * const router = new HttpActionRouter();
381
+ * router.get("/hello", { handler: () => ok({ message: "Hello!" }) });
382
+ *
383
+ * export const main = router.handler();
384
+ * ```
385
+ */
386
+ handler() {
387
+ return async (args) => {
388
+ const method = (args.__ow_method ?? "get").toUpperCase();
389
+ const rawPath = args.__ow_path ?? "/";
390
+ const path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
391
+ const headers = args.__ow_headers ?? {};
392
+ const body = parseRequestBody(args.__ow_body, args);
393
+ const query = parseQueryParams(args.__ow_query, args);
394
+ const context = await this.buildContext(args);
395
+ const matchedMethods = [];
396
+ for (const route of this.routes) {
397
+ const match = route.pattern.exec(path);
398
+ if (!match) continue;
399
+ matchedMethods.push(route.method);
400
+ if (route.method !== method) continue;
401
+ const response = await this.handleRoute(route, match, body, query, headers, method, path, context);
402
+ if (response) return response;
403
+ }
404
+ if (matchedMethods.length > 0) return methodNotAllowed(`Method ${method} not allowed`);
405
+ return notFound(`No route matches ${path}`);
406
+ };
407
+ }
408
+ };
409
+
410
+ //#endregion
411
+ //#region ../../packages-private/common-utils/source/storage/files-store.ts
412
+ /** Default directory prefix. */
413
+ const DEFAULT_DIR_PREFIX = "store";
414
+ /**
415
+ * Creates a generic key-value store backed by @adobe/aio-lib-files.
416
+ * Provides persistent storage that survives beyond TTL.
417
+ *
418
+ * @typeParam T - The type of data to store.
419
+ * @param options - Configuration options for the store.
420
+ * @returns A KeyValueStore implementation.
421
+ *
422
+ * @example
423
+ * ```typescript
424
+ * interface UserProfile {
425
+ * id: string;
426
+ * name: string;
427
+ * email: string;
428
+ * }
429
+ *
430
+ * const store = await createFilesStore<UserProfile>({
431
+ * dirPrefix: "profiles",
432
+ * });
433
+ *
434
+ * await store.put("user-123", { id: "123", name: "John", email: "john@example.com" });
435
+ * const profile = await store.get("user-123");
436
+ * ```
437
+ */
438
+ async function createFilesStore(options = {}) {
439
+ const { dirPrefix = DEFAULT_DIR_PREFIX } = options;
440
+ return new FilesStore(await init(), dirPrefix);
441
+ }
442
+ /**
443
+ * Key-value store implementation using @adobe/aio-lib-files.
444
+ */
445
+ var FilesStore = class {
446
+ constructor(files, dirPrefix) {
447
+ this.files = files;
448
+ this.dirPrefix = dirPrefix;
449
+ }
450
+ async get(key) {
451
+ const filePath = this.buildFilePath(key);
452
+ try {
453
+ const content = await this.files.read(filePath);
454
+ if (!content) return null;
455
+ return JSON.parse(content.toString("utf8"));
456
+ } catch {
457
+ return null;
458
+ }
459
+ }
460
+ async put(key, data) {
461
+ const filePath = this.buildFilePath(key);
462
+ await this.files.write(filePath, JSON.stringify(data));
463
+ }
464
+ async delete(key) {
465
+ const filePath = this.buildFilePath(key);
466
+ try {
467
+ const result = await this.files.delete(filePath);
468
+ return Array.isArray(result) && result.length > 0;
469
+ } catch {
470
+ return false;
471
+ }
472
+ }
473
+ buildFilePath(key) {
474
+ return `${this.dirPrefix}/${key}.json`;
475
+ }
476
+ };
477
+
478
+ //#endregion
479
+ //#region ../../packages-private/common-utils/source/storage/state-store.ts
480
+ /** Default TTL for state entries (3 hours in seconds). */
481
+ const DEFAULT_TTL_SECONDS = 10800;
482
+ /** Default key prefix. */
483
+ const DEFAULT_KEY_PREFIX = "store";
484
+ /**
485
+ * Creates a generic key-value store backed by @adobe/aio-lib-state.
486
+ * Provides fast, TTL-based caching for temporary data.
487
+ *
488
+ * @typeParam T - The type of data to store.
489
+ * @param options - Configuration options for the store.
490
+ * @returns A KeyValueStore implementation.
491
+ *
492
+ * @example
493
+ * ```typescript
494
+ * interface UserSession {
495
+ * userId: string;
496
+ * token: string;
497
+ * }
498
+ *
499
+ * const store = await createStateStore<UserSession>({
500
+ * keyPrefix: "session",
501
+ * ttlSeconds: 3600, // 1 hour
502
+ * });
503
+ *
504
+ * await store.put("user-123", { userId: "123", token: "abc" });
505
+ * const session = await store.get("user-123");
506
+ * ```
507
+ */
508
+ async function createStateStore(options = {}) {
509
+ const { keyPrefix = DEFAULT_KEY_PREFIX, ttlSeconds = DEFAULT_TTL_SECONDS } = options;
510
+ return new StateStore(await init$1(), keyPrefix, ttlSeconds);
511
+ }
512
+ /**
513
+ * Key-value store implementation using @adobe/aio-lib-state.
514
+ */
515
+ var StateStore = class {
516
+ constructor(state, keyPrefix, ttlSeconds) {
517
+ this.state = state;
518
+ this.keyPrefix = keyPrefix;
519
+ this.ttlSeconds = ttlSeconds;
520
+ }
521
+ async get(key) {
522
+ const fullKey = this.buildKey(key);
523
+ const result = await this.state.get(fullKey);
524
+ if (!result?.value) return null;
525
+ try {
526
+ return JSON.parse(result.value);
527
+ } catch {
528
+ return null;
529
+ }
530
+ }
531
+ async put(key, data) {
532
+ const fullKey = this.buildKey(key);
533
+ const value = JSON.stringify(data);
534
+ await this.state.put(fullKey, value, { ttl: this.ttlSeconds });
535
+ }
536
+ async delete(key) {
537
+ const fullKey = this.buildKey(key);
538
+ try {
539
+ await this.state.delete(fullKey);
540
+ return true;
541
+ } catch {
542
+ return false;
543
+ }
544
+ }
545
+ buildKey(key) {
546
+ return `${this.keyPrefix}-${key}`;
547
+ }
548
+ };
549
+
550
+ //#endregion
551
+ //#region ../../packages-private/common-utils/source/storage/combined-store.ts
552
+ /** Default TTL for cache (10 minutes). */
553
+ const DEFAULT_CACHE_TTL_SECONDS = 600;
554
+ /**
555
+ * Creates a combined key-value store that uses:
556
+ * - lib-state for fast cache during active operations
557
+ * - lib-files for persistent storage
558
+ *
559
+ * Read strategy: cache first, then persistent storage
560
+ * Write strategy:
561
+ * - Always write to cache for fast reads
562
+ * - Write to persistent storage based on shouldPersist predicate
563
+ *
564
+ * @typeParam T - The type of data to store.
565
+ * @param options - Configuration options for the stores.
566
+ *
567
+ * @example
568
+ * ```typescript
569
+ * interface Task {
570
+ * id: string;
571
+ * status: "pending" | "completed";
572
+ * result?: unknown;
573
+ * }
574
+ *
575
+ * const store = await createCombinedStore<Task>({
576
+ * cache: { keyPrefix: "task", ttlSeconds: 600 },
577
+ * persistent: {
578
+ * dirPrefix: "tasks",
579
+ * shouldPersist: (task) => task.status === "completed",
580
+ * },
581
+ * });
582
+ *
583
+ * // During processing: writes to cache only
584
+ * await store.put("task-1", { id: "1", status: "pending" });
585
+ *
586
+ * // On completion (as shouldPersist indicates): writes to both cache and persistent
587
+ * await store.put("task-1", { id: "1", status: "completed", result: {} });
588
+ * ```
589
+ */
590
+ async function createCombinedStore(options = {}) {
591
+ const { cache = {}, persistent = {} } = options;
592
+ const { shouldPersist, ...filesOptions } = persistent;
593
+ const cacheOptions = {
594
+ ttlSeconds: DEFAULT_CACHE_TTL_SECONDS,
595
+ ...cache
596
+ };
597
+ const [cacheStore, persistentStore] = await Promise.all([createStateStore(cacheOptions), createFilesStore(filesOptions)]);
598
+ return new CombinedStore(cacheStore, persistentStore, shouldPersist);
599
+ }
600
+ /**
601
+ * Combined key-value store implementation.
602
+ * Uses lib-state for cache and lib-files for persistence.
603
+ */
604
+ var CombinedStore = class {
605
+ constructor(cache, persistent, shouldPersist) {
606
+ this.cache = cache;
607
+ this.persistent = persistent;
608
+ this.shouldPersist = shouldPersist;
609
+ }
610
+ async get(key) {
611
+ const cached = await this.cache.get(key);
612
+ if (cached !== null && cached !== void 0) return cached;
613
+ const persisted = await this.persistent.get(key);
614
+ if (persisted !== null && persisted !== void 0) {
615
+ await this.cache.put(key, persisted).catch(() => {});
616
+ return persisted;
617
+ }
618
+ return null;
619
+ }
620
+ async put(key, data) {
621
+ await this.cache.put(key, data);
622
+ if (!this.shouldPersist || this.shouldPersist(data)) await this.persistent.put(key, data);
623
+ }
624
+ async delete(key) {
625
+ const [cacheDeleted, persistentDeleted] = await Promise.all([this.cache.delete?.(key) ?? Promise.resolve(false), this.persistent.delete?.(key) ?? Promise.resolve(false)]);
626
+ return cacheDeleted || persistentDeleted;
627
+ }
628
+ };
629
+
630
+ //#endregion
631
+ //#region source/management/installation/schema.ts
632
+ /** Schema for validating Adobe I/O app credentials required for installation. */
633
+ const AppDataSchema = v.object({
634
+ consumerOrgId: nonEmptyStringValueSchema("consumerOrgId"),
635
+ orgName: nonEmptyStringValueSchema("orgName"),
636
+ projectId: nonEmptyStringValueSchema("projectId"),
637
+ projectName: nonEmptyStringValueSchema("projectName"),
638
+ projectTitle: nonEmptyStringValueSchema("projectTitle"),
639
+ workspaceId: nonEmptyStringValueSchema("workspaceId"),
640
+ workspaceName: nonEmptyStringValueSchema("workspaceName"),
641
+ workspaceTitle: nonEmptyStringValueSchema("workspaceTitle")
642
+ });
643
+
644
+ //#endregion
645
+ //#region source/actions/installation.ts
646
+ const DEFAULT_ACTION_NAME = "app-management/installation";
647
+ /** Creates an installation state store using lib-core combined storage. */
648
+ function createInstallationStore() {
649
+ return createCombinedStore({
650
+ cache: { keyPrefix: "installation" },
651
+ persistent: {
652
+ dirPrefix: "installation",
653
+ shouldPersist: isCompletedState
654
+ }
655
+ });
656
+ }
657
+ /** Returns the storage key used to store the current installation ID. */
658
+ function getStorageKey() {
659
+ return "current";
660
+ }
661
+ /**
662
+ * Creates hooks to sync installation state to storage.
663
+ */
664
+ function createInstallationHooks(store, logFn) {
665
+ const logAndSave = async (message, data) => {
666
+ logFn(message);
667
+ await store.put(getStorageKey(), data);
668
+ };
669
+ return {
670
+ onInstallationStart: (state) => logAndSave("Installation started", state),
671
+ onInstallationFailure: (state) => logAndSave("Installation failed", state),
672
+ onInstallationSuccess: (state) => logAndSave("Installation succeeded", state),
673
+ onStepStart: (event, state) => logAndSave(`Step started: ${event.stepName}`, state),
674
+ onStepSuccess: (event, state) => logAndSave(`Step succeeded: ${event.stepName}`, state),
675
+ onStepFailure: (event, state) => logAndSave(`Step failed: ${event.stepName}`, state)
676
+ };
677
+ }
678
+ /**
679
+ * Installation action router.
680
+ *
681
+ * Routes:
682
+ * - POST /installation - Start installation (creates plan, invokes execution async)
683
+ * - GET /installation/execution - Get current execution status
684
+ * - POST /installation/execution - Execute installation (internal, called async)
685
+ */
686
+ const router = new HttpActionRouter().use(logger({ name: () => "installation" }));
687
+ /**
688
+ * GET /installation/execution - Get current execution status
689
+ *
690
+ * Flow:
691
+ * 1. Find execution in state store
692
+ * 2. If found: return execution plan with step statuses
693
+ * 3. If not found: return empty status
694
+ */
695
+ router.get("/", { handler: async (_req, { logger: logger$1 }) => {
696
+ logger$1.debug("Getting installation execution status...");
697
+ const state = await (await createInstallationStore()).get(getStorageKey());
698
+ if (state) {
699
+ logger$1.debug(`Found execution: ${state.status}`);
700
+ return ok({ body: state });
701
+ }
702
+ logger$1.debug("No execution found");
703
+ return noContent();
704
+ } });
705
+ /**
706
+ * POST / - Start installation
707
+ *
708
+ * Flow:
709
+ * 1. Find execution in state store
710
+ * 2. If found and (pending/in-progress or succeeded): return 409 Conflict
711
+ * 3. If not found or failed: create plan, invoke execution async, return 202 Accepted
712
+ */
713
+ router.post("/", {
714
+ body: object({
715
+ appData: AppDataSchema,
716
+ commerceBaseUrl: string(),
717
+ commerceEnv: string(),
718
+ ioEventsUrl: string(),
719
+ ioEventsEnv: string()
720
+ }),
721
+ handler: async (req, { logger: logger$1, rawParams }) => {
722
+ logger$1.debug("Starting installation...");
723
+ const store = await createInstallationStore();
724
+ const existingState = await store.get(getStorageKey());
725
+ if (existingState) {
726
+ if (isInProgressState(existingState)) {
727
+ logger$1.debug(`Installation already in progress: ${existingState.status}`);
728
+ return conflict(`Installation is already ${existingState.status}. Wait for it to complete.`);
729
+ }
730
+ if (isSucceededState(existingState)) {
731
+ logger$1.debug("Installation already succeeded");
732
+ return conflict("Installation has already completed successfully.");
733
+ }
734
+ logger$1.debug("Previous installation failed, allowing retry");
735
+ }
736
+ const appConfig = rawParams.appConfig;
737
+ if (!appConfig) return internalServerError("Could not find or parse the app.commerce.manifest.json file, is it present and valid?");
738
+ const initialState = createInitialInstallationState({ config: appConfig });
739
+ logger$1.debug(`Created initial state: ${initialState.id}`);
740
+ await store.put(getStorageKey(), initialState);
741
+ const activation = await openwhisk().actions.invoke({
742
+ name: DEFAULT_ACTION_NAME,
743
+ blocking: false,
744
+ result: false,
745
+ params: {
746
+ ...rawParams,
747
+ appData: req.body.appData,
748
+ AIO_EVENTS_API_BASE_URL: req.body.ioEventsUrl,
749
+ AIO_COMMERCE_AUTH_IMS_ENVIRONMENT: req.body.ioEventsEnv,
750
+ AIO_COMMERCE_API_BASE_URL: req.body.commerceBaseUrl,
751
+ AIO_COMMERCE_API_FLAVOR: req.body.commerceEnv,
752
+ initialState,
753
+ appConfig,
754
+ __ow_path: "/execution",
755
+ __ow_method: "post"
756
+ }
757
+ });
758
+ logger$1.debug(`Async execution started: ${activation.activationId}`);
759
+ return accepted({ body: {
760
+ message: "Installation started",
761
+ activationId: activation.activationId,
762
+ ...initialState
763
+ } });
764
+ }
765
+ });
766
+ /**
767
+ * POST /installation/execution - Execute installation (internal)
768
+ *
769
+ * This endpoint is called asynchronously by POST /installation.
770
+ * It runs the actual installation workflow and saves state.
771
+ */
772
+ router.post("/execution", { handler: async (_req, { logger: logger$1, rawParams }) => {
773
+ const { appData, ...params } = rawParams;
774
+ const { initialState, appConfig } = params;
775
+ if (!initialState) return badRequest("initialState is required for execution");
776
+ if (!appConfig) return badRequest("appConfig is required for execution");
777
+ const store = await createInstallationStore();
778
+ const hooks = createInstallationHooks(store, (msg) => logger$1.debug(msg));
779
+ const installationContext = {
780
+ appData,
781
+ params,
782
+ logger: logger$1,
783
+ customScripts: params.customScriptsLoader?.(appConfig, logger$1) || {}
784
+ };
785
+ logger$1.debug(`Executing installation: ${initialState.id}`);
786
+ const result = await runInstallation({
787
+ installationContext,
788
+ config: appConfig,
789
+ initialState,
790
+ hooks
791
+ });
792
+ await store.put(getStorageKey(), result);
793
+ logger$1.debug(`Installation completed: ${result.status}`);
794
+ if (isFailedState(result)) return internalServerError({ body: {
795
+ message: "Installation failed",
796
+ error: result.error,
797
+ state: result
798
+ } });
799
+ return ok({ body: result });
800
+ } });
801
+ /**
802
+ * DELETE / - Clear installation state
803
+ *
804
+ * This endpoint allows clearing the installation state.
805
+ */
806
+ router.delete("/", { handler: async (_req, { logger: logger$1 }) => {
807
+ logger$1.debug("Clearing installation state...");
808
+ await (await createInstallationStore()).delete(getStorageKey());
809
+ logger$1.debug("Installation state cleared");
810
+ return noContent();
811
+ } });
812
+ /** The route handler for the runtime action. */
813
+ const installationRuntimeAction = ({ appConfig, customScriptsLoader }) => async (params) => {
814
+ return await router.handler()({
815
+ ...params,
816
+ appConfig,
817
+ customScriptsLoader
818
+ });
819
+ };
820
+
821
+ //#endregion
822
+ export { installationRuntimeAction };