@unrdf/serverless 26.4.2
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 +305 -0
- package/package.json +81 -0
- package/src/api/api-gateway-config.mjs +390 -0
- package/src/api/index.mjs +12 -0
- package/src/cdk/index.mjs +6 -0
- package/src/cdk/unrdf-stack.mjs +363 -0
- package/src/deploy/index.mjs +10 -0
- package/src/deploy/lambda-bundler.mjs +310 -0
- package/src/index.mjs +91 -0
- package/src/storage/dynamodb-adapter.mjs +324 -0
- package/src/storage/index.mjs +6 -0
package/src/index.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview UNRDF Serverless - One-click AWS deployment for RDF applications
|
|
3
|
+
*
|
|
4
|
+
* @description
|
|
5
|
+
* Serverless deployment toolkit for UNRDF applications with:
|
|
6
|
+
* - AWS CDK infrastructure as code
|
|
7
|
+
* - Lambda function bundling with esbuild
|
|
8
|
+
* - API Gateway REST endpoints
|
|
9
|
+
* - DynamoDB RDF storage
|
|
10
|
+
* - CloudFront CDN integration
|
|
11
|
+
*
|
|
12
|
+
* @module serverless
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
* @license MIT
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```javascript
|
|
18
|
+
* import { createUNRDFStack, LambdaBundler, ApiGatewayConfig } from '@unrdf/serverless';
|
|
19
|
+
*
|
|
20
|
+
* // Create CDK stack
|
|
21
|
+
* const stack = createUNRDFStack(app, 'Production', {
|
|
22
|
+
* environment: 'prod',
|
|
23
|
+
* memorySizeMb: 2048,
|
|
24
|
+
* enableCdn: true
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Bundle Lambda functions
|
|
28
|
+
* const bundler = new LambdaBundler({
|
|
29
|
+
* entryPoint: './src/handler.mjs',
|
|
30
|
+
* outDir: './dist/lambda'
|
|
31
|
+
* });
|
|
32
|
+
* await bundler.bundle();
|
|
33
|
+
*
|
|
34
|
+
* // Configure API Gateway
|
|
35
|
+
* const apiConfig = new ApiGatewayConfig('my-api')
|
|
36
|
+
* .addEndpoint('/query', 'POST', 'queryFunction')
|
|
37
|
+
* .build();
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// CDK Infrastructure
|
|
42
|
+
export { UNRDFStack, createUNRDFStack } from './cdk/unrdf-stack.mjs';
|
|
43
|
+
|
|
44
|
+
// Lambda Bundling
|
|
45
|
+
export {
|
|
46
|
+
LambdaBundler,
|
|
47
|
+
createDefaultBundlerConfig,
|
|
48
|
+
bundleUNRDFFunctions,
|
|
49
|
+
} from './deploy/lambda-bundler.mjs';
|
|
50
|
+
|
|
51
|
+
// API Gateway
|
|
52
|
+
export {
|
|
53
|
+
ApiGatewayConfig,
|
|
54
|
+
createDefaultApiConfig,
|
|
55
|
+
validateApiRequest,
|
|
56
|
+
createApiResponse,
|
|
57
|
+
createErrorResponse,
|
|
58
|
+
} from './api/api-gateway-config.mjs';
|
|
59
|
+
|
|
60
|
+
// DynamoDB Storage
|
|
61
|
+
export { DynamoDBAdapter, createAdapterFromEnv } from './storage/dynamodb-adapter.mjs';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Package version
|
|
65
|
+
* @constant {string}
|
|
66
|
+
*/
|
|
67
|
+
export const VERSION = '1.0.0';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Supported AWS regions
|
|
71
|
+
* @constant {string[]}
|
|
72
|
+
*/
|
|
73
|
+
export const SUPPORTED_REGIONS = [
|
|
74
|
+
'us-east-1',
|
|
75
|
+
'us-west-2',
|
|
76
|
+
'eu-west-1',
|
|
77
|
+
'eu-central-1',
|
|
78
|
+
'ap-southeast-1',
|
|
79
|
+
'ap-northeast-1',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Default configuration
|
|
84
|
+
* @constant {Object}
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_CONFIG = {
|
|
87
|
+
runtime: 'nodejs20.x',
|
|
88
|
+
memorySizeMb: 1024,
|
|
89
|
+
timeoutSeconds: 30,
|
|
90
|
+
environment: 'dev',
|
|
91
|
+
};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview DynamoDB RDF Storage Adapter - Persistent triple storage
|
|
3
|
+
*
|
|
4
|
+
* @description
|
|
5
|
+
* DynamoDB adapter for RDF triple storage with optimized query patterns.
|
|
6
|
+
* Supports subject-predicate-object queries with global secondary indexes.
|
|
7
|
+
*
|
|
8
|
+
* @module serverless/storage/dynamodb-adapter
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
* @license MIT
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* RDF triple schema
|
|
17
|
+
* @typedef {Object} Triple
|
|
18
|
+
* @property {string} subject - Triple subject
|
|
19
|
+
* @property {string} predicate - Triple predicate
|
|
20
|
+
* @property {string} object - Triple object
|
|
21
|
+
* @property {string} [graph] - Named graph URI
|
|
22
|
+
*/
|
|
23
|
+
const TripleSchema = z.object({
|
|
24
|
+
subject: z.string(),
|
|
25
|
+
predicate: z.string(),
|
|
26
|
+
object: z.string(),
|
|
27
|
+
graph: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* DynamoDB RDF Storage Adapter
|
|
32
|
+
*
|
|
33
|
+
* @class DynamoDBAdapter
|
|
34
|
+
*
|
|
35
|
+
* @description
|
|
36
|
+
* High-performance RDF storage adapter for DynamoDB with:
|
|
37
|
+
* - Optimized triple patterns (SPO, PSO, OSP)
|
|
38
|
+
* - Global secondary indexes for queries
|
|
39
|
+
* - Batch operations for bulk import
|
|
40
|
+
* - Pagination support for large result sets
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```javascript
|
|
44
|
+
* import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
45
|
+
* import { DynamoDBAdapter } from '@unrdf/serverless/storage';
|
|
46
|
+
*
|
|
47
|
+
* const client = new DynamoDBClient({});
|
|
48
|
+
* const adapter = new DynamoDBAdapter(client, 'triples-table');
|
|
49
|
+
*
|
|
50
|
+
* await adapter.addTriple({
|
|
51
|
+
* subject: 'http://example.org/alice',
|
|
52
|
+
* predicate: 'http://xmlns.com/foaf/0.1/name',
|
|
53
|
+
* object: '"Alice"'
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export class DynamoDBAdapter {
|
|
58
|
+
/**
|
|
59
|
+
* DynamoDB client
|
|
60
|
+
* @type {Object}
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
#client;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Table name
|
|
67
|
+
* @type {string}
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
#tableName;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create DynamoDB adapter
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} client - DynamoDB client
|
|
76
|
+
* @param {string} tableName - Table name
|
|
77
|
+
*/
|
|
78
|
+
constructor(client, tableName) {
|
|
79
|
+
this.#client = client;
|
|
80
|
+
this.#tableName = tableName;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Add RDF triple to store
|
|
85
|
+
*
|
|
86
|
+
* @param {Triple} triple - RDF triple
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
*
|
|
89
|
+
* @throws {Error} If add operation fails
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```javascript
|
|
93
|
+
* await adapter.addTriple({
|
|
94
|
+
* subject: 'http://example.org/alice',
|
|
95
|
+
* predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
|
|
96
|
+
* object: 'http://xmlns.com/foaf/0.1/Person'
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
async addTriple(triple) {
|
|
101
|
+
const validated = TripleSchema.parse(triple);
|
|
102
|
+
|
|
103
|
+
const item = {
|
|
104
|
+
subject: { S: validated.subject },
|
|
105
|
+
predicate_object: { S: `${validated.predicate}#${validated.object}` },
|
|
106
|
+
predicate: { S: validated.predicate },
|
|
107
|
+
object: { S: validated.object },
|
|
108
|
+
subject_object: { S: `${validated.subject}#${validated.object}` },
|
|
109
|
+
subject_predicate: { S: `${validated.subject}#${validated.predicate}` },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (validated.graph) {
|
|
113
|
+
item.graph = { S: validated.graph };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await this.#client.send({
|
|
118
|
+
TableName: this.#tableName,
|
|
119
|
+
Item: item,
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new Error(`Failed to add triple: ${error.message}`, { cause: error });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Add multiple triples in batch
|
|
128
|
+
*
|
|
129
|
+
* @param {Triple[]} triples - Array of triples
|
|
130
|
+
* @param {number} [batchSize=25] - Batch size (max 25 for DynamoDB)
|
|
131
|
+
* @returns {Promise<number>} Number of triples added
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```javascript
|
|
135
|
+
* const triples = [
|
|
136
|
+
* { subject: 's1', predicate: 'p1', object: 'o1' },
|
|
137
|
+
* { subject: 's2', predicate: 'p2', object: 'o2' }
|
|
138
|
+
* ];
|
|
139
|
+
* const count = await adapter.addTriples(triples);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async addTriples(triples, batchSize = 25) {
|
|
143
|
+
let added = 0;
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < triples.length; i += batchSize) {
|
|
146
|
+
const batch = triples.slice(i, i + batchSize);
|
|
147
|
+
await Promise.all(batch.map(triple => this.addTriple(triple)));
|
|
148
|
+
added += batch.length;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return added;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Query triples by pattern
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} pattern - Query pattern
|
|
158
|
+
* @param {string} [pattern.subject] - Subject filter
|
|
159
|
+
* @param {string} [pattern.predicate] - Predicate filter
|
|
160
|
+
* @param {string} [pattern.object] - Object filter
|
|
161
|
+
* @param {number} [limit=100] - Maximum results
|
|
162
|
+
* @returns {Promise<Triple[]>} Matching triples
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```javascript
|
|
166
|
+
* // Query all triples with specific subject
|
|
167
|
+
* const triples = await adapter.queryTriples({
|
|
168
|
+
* subject: 'http://example.org/alice'
|
|
169
|
+
* });
|
|
170
|
+
*
|
|
171
|
+
* // Query with subject and predicate
|
|
172
|
+
* const triples = await adapter.queryTriples({
|
|
173
|
+
* subject: 'http://example.org/alice',
|
|
174
|
+
* predicate: 'http://xmlns.com/foaf/0.1/name'
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
async queryTriples(pattern, limit = 100) {
|
|
179
|
+
const { subject, predicate, object } = pattern;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// SPO: Query by subject
|
|
183
|
+
if (subject && !predicate && !object) {
|
|
184
|
+
return await this.#queryBySubject(subject, limit);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// PSO: Query by predicate using GSI
|
|
188
|
+
if (predicate && !subject && !object) {
|
|
189
|
+
return await this.#queryByPredicate(predicate, limit);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// OSP: Query by object using GSI
|
|
193
|
+
if (object && !subject && !predicate) {
|
|
194
|
+
return await this.#queryByObject(object, limit);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// SPO: Query by subject and predicate
|
|
198
|
+
if (subject && predicate) {
|
|
199
|
+
return await this.#queryBySubjectPredicate(subject, predicate, limit);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Full scan if no filters (expensive!)
|
|
203
|
+
return await this.#scanAll(limit);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(`Query failed: ${error.message}`, { cause: error });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Delete triple
|
|
211
|
+
*
|
|
212
|
+
* @param {Triple} triple - Triple to delete
|
|
213
|
+
* @returns {Promise<boolean>} True if deleted
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```javascript
|
|
217
|
+
* await adapter.deleteTriple({
|
|
218
|
+
* subject: 'http://example.org/alice',
|
|
219
|
+
* predicate: 'http://xmlns.com/foaf/0.1/name',
|
|
220
|
+
* object: '"Alice"'
|
|
221
|
+
* });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
async deleteTriple(triple) {
|
|
225
|
+
const validated = TripleSchema.parse(triple);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await this.#client.send({
|
|
229
|
+
TableName: this.#tableName,
|
|
230
|
+
Key: {
|
|
231
|
+
subject: { S: validated.subject },
|
|
232
|
+
predicate_object: { S: `${validated.predicate}#${validated.object}` },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
return true;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw new Error(`Failed to delete triple: ${error.message}`, { cause: error });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Count triples matching pattern
|
|
243
|
+
*
|
|
244
|
+
* @param {Object} [pattern={}] - Query pattern
|
|
245
|
+
* @returns {Promise<number>} Triple count
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```javascript
|
|
249
|
+
* const count = await adapter.countTriples({ subject: 'http://example.org/alice' });
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
async countTriples(pattern = {}) {
|
|
253
|
+
const triples = await this.queryTriples(pattern, Number.MAX_SAFE_INTEGER);
|
|
254
|
+
return triples.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Query by subject
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
async #queryBySubject(_subject, _limit) {
|
|
262
|
+
// Implementation placeholder - requires @aws-sdk/client-dynamodb
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Query by predicate using GSI
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
async #queryByPredicate(_predicate, _limit) {
|
|
271
|
+
// Implementation placeholder
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Query by object using GSI
|
|
277
|
+
* @private
|
|
278
|
+
*/
|
|
279
|
+
async #queryByObject(_object, _limit) {
|
|
280
|
+
// Implementation placeholder
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Query by subject and predicate
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
async #queryBySubjectPredicate(_subject, _predicate, _limit) {
|
|
289
|
+
// Implementation placeholder
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Scan all triples (expensive!)
|
|
295
|
+
* @private
|
|
296
|
+
*/
|
|
297
|
+
async #scanAll(_limit) {
|
|
298
|
+
// Implementation placeholder
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create DynamoDB adapter from environment
|
|
305
|
+
*
|
|
306
|
+
* @returns {DynamoDBAdapter} Configured adapter
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```javascript
|
|
310
|
+
* // In Lambda function
|
|
311
|
+
* const adapter = createAdapterFromEnv();
|
|
312
|
+
* await adapter.addTriple({ subject: 's', predicate: 'p', object: 'o' });
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export function createAdapterFromEnv() {
|
|
316
|
+
const tableName = process.env.TRIPLES_TABLE;
|
|
317
|
+
if (!tableName) {
|
|
318
|
+
throw new Error('TRIPLES_TABLE environment variable not set');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Placeholder - requires @aws-sdk/client-dynamodb in runtime
|
|
322
|
+
const client = {}; // new DynamoDBClient({});
|
|
323
|
+
return new DynamoDBAdapter(client, tableName);
|
|
324
|
+
}
|