@aligent/appbuilder-util-lib 0.2.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.
- package/README.md +31 -0
- package/docs/functions/get.md +42 -0
- package/docs/functions/put.md +49 -0
- package/docs/modules/aio-persistent-state.md +39 -0
- package/docs/modules.md +9 -0
- package/package.json +20 -0
- package/src/aio-persistent-state/aio-persistent-state.js +151 -0
- package/src/index.js +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# App Builder Utilities Library
|
|
2
|
+
|
|
3
|
+
This library includes utility functions to simplify & standardise common
|
|
4
|
+
Adobe App Builder tasks.
|
|
5
|
+
|
|
6
|
+
## Documentation
|
|
7
|
+
|
|
8
|
+
Documentation on each function can be found [here](docs/modules.md)
|
|
9
|
+
|
|
10
|
+
## Build
|
|
11
|
+
|
|
12
|
+
This library is written in typescript and can be built using:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx nx build appbuilder-util-lib
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install @aligent/appbuilder-util-lib
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Testing & Linting
|
|
25
|
+
|
|
26
|
+
Vitest tests, linting & type-checking can be run with
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx nx test appbuilder-util-lib
|
|
30
|
+
npx nx lint appbuilder-util-lib
|
|
31
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[**@aligent/appbuilder-util-lib**](../modules.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@aligent/appbuilder-util-lib](../modules.md) / [aio-persistent-state](../modules/aio-persistent-state.md) / PersistentState.get
|
|
6
|
+
|
|
7
|
+
# Function: PersistentState.get()
|
|
8
|
+
|
|
9
|
+
> **get**(`key`): `Promise`\<`string`\>
|
|
10
|
+
|
|
11
|
+
Retrieves a value by key, using State as a cache layer in front of Files.
|
|
12
|
+
|
|
13
|
+
1. Attempts to read from State (fast, but subject to TTL expiry).
|
|
14
|
+
2. On a cache miss, falls back to Files (persistent, no TTL).
|
|
15
|
+
3. If the value from Files is within the 1 MB State limit, it is
|
|
16
|
+
written back into State so subsequent reads are served from cache.
|
|
17
|
+
|
|
18
|
+
## Parameters
|
|
19
|
+
|
|
20
|
+
### key
|
|
21
|
+
|
|
22
|
+
`string`
|
|
23
|
+
|
|
24
|
+
The key to look up.
|
|
25
|
+
|
|
26
|
+
## Returns
|
|
27
|
+
|
|
28
|
+
`Promise`\<`string`\>
|
|
29
|
+
|
|
30
|
+
The stored string value.
|
|
31
|
+
|
|
32
|
+
## Throws
|
|
33
|
+
|
|
34
|
+
If the encoded key exceeds the maximum size or both storage layers fail.
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { PersistentState } from '@aligent/appbuilder-util-lib';
|
|
40
|
+
|
|
41
|
+
const value = await PersistentState.get('myKey');
|
|
42
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[**@aligent/appbuilder-util-lib**](../modules.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@aligent/appbuilder-util-lib](../modules.md) / [aio-persistent-state](../modules/aio-persistent-state.md) / PersistentState.put
|
|
6
|
+
|
|
7
|
+
# Function: PersistentState.put()
|
|
8
|
+
|
|
9
|
+
> **put**(`key`, `value`): `Promise`\<`string`\>
|
|
10
|
+
|
|
11
|
+
Stores a value by key, writing through to both Files and State.
|
|
12
|
+
|
|
13
|
+
1. Always writes to Files first to guarantee durability (no TTL).
|
|
14
|
+
2. If the value is within the 1 MB State limit, it is also written to
|
|
15
|
+
State for fast subsequent reads.
|
|
16
|
+
3. Values exceeding 1 MB are stored in Files only; State is skipped
|
|
17
|
+
because it cannot hold entries larger than 1 MB.
|
|
18
|
+
|
|
19
|
+
## Parameters
|
|
20
|
+
|
|
21
|
+
### key
|
|
22
|
+
|
|
23
|
+
`string`
|
|
24
|
+
|
|
25
|
+
The key under which to store the value.
|
|
26
|
+
|
|
27
|
+
### value
|
|
28
|
+
|
|
29
|
+
`string`
|
|
30
|
+
|
|
31
|
+
The string value to store.
|
|
32
|
+
|
|
33
|
+
## Returns
|
|
34
|
+
|
|
35
|
+
`Promise`\<`string`\>
|
|
36
|
+
|
|
37
|
+
The base64url-encoded key.
|
|
38
|
+
|
|
39
|
+
## Throws
|
|
40
|
+
|
|
41
|
+
If the encoded key exceeds the maximum size or a storage operation fails.
|
|
42
|
+
|
|
43
|
+
## Example
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { PersistentState } from '@aligent/appbuilder-util-lib';
|
|
47
|
+
|
|
48
|
+
const encodedKey = await PersistentState.put('myKey', 'myValue');
|
|
49
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[**@aligent/appbuilder-util-lib**](../modules.md)
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
[@aligent/appbuilder-util-lib](../modules.md) / aio-persistent-state
|
|
6
|
+
|
|
7
|
+
# Module: aio-persistent-state
|
|
8
|
+
|
|
9
|
+
A utility library for persistent, high-performance key-value storage
|
|
10
|
+
using Adobe I/O State (for caching) and Adobe I/O File (for durability).
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
This library provides a high-level abstraction for key-value storage using Adobe App Builder's runtime services. It combines:
|
|
15
|
+
- **Adobe I/O Lib State** — fast access and short-term caching
|
|
16
|
+
- **Adobe I/O Lib Files** — long-term durability and backup
|
|
17
|
+
|
|
18
|
+
### Why both State and Files?
|
|
19
|
+
|
|
20
|
+
Adobe I/O State enforces a **TTL (Time-To-Live)** on all entries, so cached data will eventually expire and be evicted. Files storage has no such expiration, making it suitable for persistent, long-lived data. By writing to Files for durability and using State as a fast-access cache, we get speed from State and persistence from Files.
|
|
21
|
+
|
|
22
|
+
### 1 MB State size limit
|
|
23
|
+
|
|
24
|
+
Adobe I/O State imposes a **maximum value size of 1 MB** per entry. This library works around the limit automatically:
|
|
25
|
+
- **On write (`put`)**: values exceeding 1 MB are stored in Files only; the State cache is skipped.
|
|
26
|
+
- **On read (`get`)**: if the value retrieved from Files exceeds 1 MB, it is returned directly without being cached in State.
|
|
27
|
+
|
|
28
|
+
Values within the 1 MB limit are stored in both layers so that subsequent reads are served from the faster State cache.
|
|
29
|
+
|
|
30
|
+
### How it works
|
|
31
|
+
|
|
32
|
+
- Data is retrieved quickly from State if present (cache hit)
|
|
33
|
+
- If not found in State (cache miss, e.g. due to TTL expiry), the library loads from Files, then re-populates the State cache
|
|
34
|
+
- On updates, the value is written to Files first for durability, then cached in State
|
|
35
|
+
|
|
36
|
+
## Functions
|
|
37
|
+
|
|
38
|
+
- [PersistentState.get](../functions/get.md)
|
|
39
|
+
- [PersistentState.put](../functions/put.md)
|
package/docs/modules.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aligent/appbuilder-util-lib",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A utility library to simplify and standardise common Adobe App Builder tasks.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"typings": "./src/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/aligent/microservice-development-utilities.git",
|
|
11
|
+
"directory": "packages/appbuilder-util-lib"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@adobe/aio-sdk": "^6.0.0",
|
|
15
|
+
"base64url": "^3.0.1"
|
|
16
|
+
},
|
|
17
|
+
"author": "Aligent",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"types": "./src/index.d.ts"
|
|
20
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.get = get;
|
|
7
|
+
exports.put = put;
|
|
8
|
+
/**
|
|
9
|
+
* @module aio-persistent-state
|
|
10
|
+
*
|
|
11
|
+
* A two-tier key-value storage library for Adobe App Builder runtime that
|
|
12
|
+
* combines Adobe I/O State and Adobe I/O Files to achieve both fast access
|
|
13
|
+
* and long-term durability.
|
|
14
|
+
*
|
|
15
|
+
* **Why both State and Files?**
|
|
16
|
+
* Adobe I/O State enforces a TTL (Time-To-Live) on all entries, meaning
|
|
17
|
+
* cached data will expire and be evicted automatically. Files storage has
|
|
18
|
+
* no such expiration, making it suitable for persistent, long-lived data.
|
|
19
|
+
* By writing to Files for durability and using State as a fast-access cache,
|
|
20
|
+
* we get the best of both: speed from State and persistence from Files.
|
|
21
|
+
*
|
|
22
|
+
* **1MB size limit:**
|
|
23
|
+
* Adobe I/O State imposes a maximum value size of 1 MB per entry. Values
|
|
24
|
+
* that exceed this limit are stored in Files only, bypassing the State
|
|
25
|
+
* cache entirely. On read, such values are served directly from Files
|
|
26
|
+
* without being cached in State.
|
|
27
|
+
*/
|
|
28
|
+
const aio_sdk_1 = require("@adobe/aio-sdk");
|
|
29
|
+
const base64url_1 = __importDefault(require("base64url"));
|
|
30
|
+
const logger = aio_sdk_1.Core.Logger('aio-persistent-state', { level: 'info' });
|
|
31
|
+
let stateLibPromise;
|
|
32
|
+
let filesLibPromise;
|
|
33
|
+
/**
|
|
34
|
+
* Returns a lazily-initialised Adobe I/O State instance.
|
|
35
|
+
* The promise is cached so the SDK is only initialised once per process.
|
|
36
|
+
* If initialisation fails, the cache is cleared so the next call retries.
|
|
37
|
+
*
|
|
38
|
+
* @returns A promise that resolves to the Adobe I/O State instance.
|
|
39
|
+
*/
|
|
40
|
+
function getStateLib() {
|
|
41
|
+
stateLibPromise ??= aio_sdk_1.State.init().catch(err => {
|
|
42
|
+
stateLibPromise = undefined;
|
|
43
|
+
throw err;
|
|
44
|
+
});
|
|
45
|
+
return stateLibPromise;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns a lazily-initialised Adobe I/O Files instance.
|
|
49
|
+
* The promise is cached so the SDK is only initialised once per process.
|
|
50
|
+
* If initialisation fails, the cache is cleared so the next call retries.
|
|
51
|
+
*
|
|
52
|
+
* @returns A promise that resolves to the Adobe I/O Files instance.
|
|
53
|
+
*/
|
|
54
|
+
function getFilesLib() {
|
|
55
|
+
filesLibPromise ??= aio_sdk_1.File.init().catch(err => {
|
|
56
|
+
filesLibPromise = undefined;
|
|
57
|
+
throw err;
|
|
58
|
+
});
|
|
59
|
+
return filesLibPromise;
|
|
60
|
+
}
|
|
61
|
+
/** Maximum length (in characters) of a base64url-encoded key accepted by State. */
|
|
62
|
+
const MAX_KEY_SIZE = 1024;
|
|
63
|
+
/** Maximum value size (in bytes) accepted by Adobe I/O State (1 MB). */
|
|
64
|
+
const MAX_VALUE_SIZE = 1024 * 1024; // 1MB
|
|
65
|
+
/**
|
|
66
|
+
* Encodes a key using base64url so it is safe for use in Adobe I/O State,
|
|
67
|
+
* which requires keys to match the pattern `/^[a-zA-Z0-9-_.]{1,1024}$/`.
|
|
68
|
+
*
|
|
69
|
+
* @param key - The original key string.
|
|
70
|
+
* @returns The base64url-encoded key.
|
|
71
|
+
* @throws {Error} If the encoded key exceeds 1024 characters ({@link MAX_KEY_SIZE}).
|
|
72
|
+
*/
|
|
73
|
+
function encodeKey(key) {
|
|
74
|
+
const encodedKey = base64url_1.default.encode(key);
|
|
75
|
+
if (encodedKey.length > MAX_KEY_SIZE) {
|
|
76
|
+
throw new Error(`Encoded key exceeds maximum size of ${MAX_KEY_SIZE} characters`);
|
|
77
|
+
}
|
|
78
|
+
return encodedKey;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Retrieves a value by key, using State as a cache layer in front of Files.
|
|
82
|
+
*
|
|
83
|
+
* 1. Attempts to read from State (fast, but subject to TTL expiry).
|
|
84
|
+
* 2. On a cache miss, falls back to Files (persistent, no TTL).
|
|
85
|
+
* 3. If the value from Files is within the 1 MB State limit, it is
|
|
86
|
+
* written back into State so subsequent reads are served from cache.
|
|
87
|
+
*
|
|
88
|
+
* @param key - The key to look up.
|
|
89
|
+
* @returns The stored string value.
|
|
90
|
+
* @throws {Error} If the encoded key exceeds the maximum size or both
|
|
91
|
+
* storage layers fail.
|
|
92
|
+
*/
|
|
93
|
+
async function get(key) {
|
|
94
|
+
try {
|
|
95
|
+
const stateLib = await getStateLib();
|
|
96
|
+
const encodedKey = encodeKey(key);
|
|
97
|
+
// Try to retrieve from State cache (may be missing due to TTL expiry)
|
|
98
|
+
const cache = await stateLib.get(encodedKey);
|
|
99
|
+
if (cache?.value != null) {
|
|
100
|
+
return cache.value;
|
|
101
|
+
}
|
|
102
|
+
// Fallback: read from Files (persistent, no TTL)
|
|
103
|
+
const filesLib = await getFilesLib();
|
|
104
|
+
const buffer = await filesLib.read(`${key}.json`);
|
|
105
|
+
const value = buffer.toString();
|
|
106
|
+
// Re-populate State cache if value is within the 1MB size limit
|
|
107
|
+
if (Buffer.byteLength(value) <= MAX_VALUE_SIZE) {
|
|
108
|
+
await stateLib.put(encodedKey, value);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger.error(`Failed to get key: ${key}`, JSON.stringify(error, null, 2));
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stores a value by key, writing through to both Files and State.
|
|
119
|
+
*
|
|
120
|
+
* 1. Always writes to Files first to guarantee durability (no TTL).
|
|
121
|
+
* 2. If the value is within the 1 MB State limit, it is also written to
|
|
122
|
+
* State for fast subsequent reads.
|
|
123
|
+
* 3. Values exceeding 1 MB are stored in Files only; State is skipped
|
|
124
|
+
* because it cannot hold entries larger than 1 MB.
|
|
125
|
+
*
|
|
126
|
+
* @param key - The key under which to store the value.
|
|
127
|
+
* @param value - The string value to store.
|
|
128
|
+
* @returns The base64url-encoded key.
|
|
129
|
+
* @throws {Error} If the encoded key exceeds the maximum size or a
|
|
130
|
+
* storage operation fails.
|
|
131
|
+
*/
|
|
132
|
+
async function put(key, value) {
|
|
133
|
+
try {
|
|
134
|
+
const encodedKey = encodeKey(key);
|
|
135
|
+
const filesLib = await getFilesLib();
|
|
136
|
+
// Always write to Files first for durability (no TTL)
|
|
137
|
+
await filesLib.write(`${key}.json`, value);
|
|
138
|
+
// Values exceeding the 1MB State limit are stored in Files only
|
|
139
|
+
if (Buffer.byteLength(value) > MAX_VALUE_SIZE) {
|
|
140
|
+
logger.warn(`Value for key "${key}" exceeds ${MAX_VALUE_SIZE} bytes, storing in file only`);
|
|
141
|
+
return encodedKey;
|
|
142
|
+
}
|
|
143
|
+
// Also cache in State for fast access (subject to TTL expiry)
|
|
144
|
+
const stateLib = await getStateLib();
|
|
145
|
+
return await stateLib.put(encodedKey, value);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
logger.error(`Failed to put key: ${key}`, JSON.stringify(error, null, 2));
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Main entry point for @aligent/appbuilder-util-lib
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.PersistentState = void 0;
|
|
40
|
+
const PersistentState = __importStar(require("./aio-persistent-state/aio-persistent-state"));
|
|
41
|
+
exports.PersistentState = PersistentState;
|