effortless-aws 0.36.1 → 0.38.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.
@@ -0,0 +1,243 @@
1
+ import {
2
+ lazyImport
3
+ } from "./chunk-U56MLLWP.js";
4
+
5
+ // src/runtime/table-client.ts
6
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
7
+ var loadSdk = () => lazyImport("@aws-sdk/client-dynamodb");
8
+ var GSI_TAG_PK = "tag-pk-index";
9
+ var marshallKey = (key) => marshall(key, { removeUndefinedValues: true });
10
+ var buildSkCondition = (sk) => {
11
+ const names = { "#sk": "sk" };
12
+ if (typeof sk === "string") {
13
+ return { expression: "AND #sk = :sk", names, values: { ":sk": sk } };
14
+ }
15
+ if ("begins_with" in sk) {
16
+ return { expression: "AND begins_with(#sk, :sk)", names, values: { ":sk": sk.begins_with } };
17
+ }
18
+ if ("gt" in sk) {
19
+ return { expression: "AND #sk > :sk", names, values: { ":sk": sk.gt } };
20
+ }
21
+ if ("gte" in sk) {
22
+ return { expression: "AND #sk >= :sk", names, values: { ":sk": sk.gte } };
23
+ }
24
+ if ("lt" in sk) {
25
+ return { expression: "AND #sk < :sk", names, values: { ":sk": sk.lt } };
26
+ }
27
+ if ("lte" in sk) {
28
+ return { expression: "AND #sk <= :sk", names, values: { ":sk": sk.lte } };
29
+ }
30
+ if ("between" in sk) {
31
+ return { expression: "AND #sk BETWEEN :sk1 AND :sk2", names, values: { ":sk1": sk.between[0], ":sk2": sk.between[1] } };
32
+ }
33
+ return { expression: "", names: {}, values: {} };
34
+ };
35
+ var createTableClient = async (tableName, options) => {
36
+ const { DynamoDB: DynamoDBClient } = await loadSdk();
37
+ let client = null;
38
+ const getClient = () => client ??= new DynamoDBClient({});
39
+ const tagField = options?.tagField ?? "tag";
40
+ return {
41
+ tableName,
42
+ async put(item, putOptions) {
43
+ const dataObj = item.data;
44
+ const tag = dataObj?.[tagField] || "";
45
+ if (!tag) throw new Error(`tag is required: data must include a "${tagField}" field`);
46
+ const dynamoItem = {
47
+ pk: item.pk,
48
+ sk: item.sk,
49
+ tag,
50
+ data: item.data
51
+ };
52
+ if (item.ttl !== void 0) dynamoItem.ttl = item.ttl;
53
+ await getClient().putItem({
54
+ TableName: tableName,
55
+ Item: marshall(dynamoItem, { removeUndefinedValues: true }),
56
+ ...putOptions?.ifNotExists ? { ConditionExpression: "attribute_not_exists(pk)" } : {}
57
+ });
58
+ },
59
+ async get(key) {
60
+ const result = await getClient().getItem({
61
+ TableName: tableName,
62
+ Key: marshallKey(key)
63
+ });
64
+ return result.Item ? unmarshall(result.Item) : void 0;
65
+ },
66
+ async delete(key) {
67
+ await getClient().deleteItem({
68
+ TableName: tableName,
69
+ Key: marshallKey(key)
70
+ });
71
+ },
72
+ async update(key, actions) {
73
+ const names = {};
74
+ const values = {};
75
+ const setClauses = [];
76
+ const removeClauses = [];
77
+ let counter = 0;
78
+ const DATA_ALIAS = "#data";
79
+ let needsDataAlias = false;
80
+ if (actions.set) {
81
+ for (const [attr, val] of Object.entries(actions.set)) {
82
+ needsDataAlias = true;
83
+ const alias = `#a${counter}`;
84
+ const valAlias = `:v${counter}`;
85
+ names[alias] = attr;
86
+ values[valAlias] = val;
87
+ setClauses.push(`${DATA_ALIAS}.${alias} = ${valAlias}`);
88
+ counter++;
89
+ }
90
+ }
91
+ if (actions.increment) {
92
+ for (const [attr, val] of Object.entries(actions.increment)) {
93
+ needsDataAlias = true;
94
+ const alias = `#a${counter}`;
95
+ const valAlias = `:v${counter}`;
96
+ const zeroAlias = `:zero${counter}`;
97
+ names[alias] = attr;
98
+ values[valAlias] = val;
99
+ values[zeroAlias] = 0;
100
+ setClauses.push(`${DATA_ALIAS}.${alias} = if_not_exists(${DATA_ALIAS}.${alias}, ${zeroAlias}) + ${valAlias}`);
101
+ counter++;
102
+ }
103
+ }
104
+ if (actions.append) {
105
+ for (const [attr, val] of Object.entries(actions.append)) {
106
+ needsDataAlias = true;
107
+ const alias = `#a${counter}`;
108
+ const valAlias = `:v${counter}`;
109
+ const emptyAlias = `:empty${counter}`;
110
+ names[alias] = attr;
111
+ values[valAlias] = val;
112
+ values[emptyAlias] = [];
113
+ setClauses.push(`${DATA_ALIAS}.${alias} = list_append(if_not_exists(${DATA_ALIAS}.${alias}, ${emptyAlias}), ${valAlias})`);
114
+ counter++;
115
+ }
116
+ }
117
+ if (actions.remove) {
118
+ for (const attr of actions.remove) {
119
+ needsDataAlias = true;
120
+ const alias = `#a${counter}`;
121
+ names[alias] = attr;
122
+ removeClauses.push(`${DATA_ALIAS}.${alias}`);
123
+ counter++;
124
+ }
125
+ }
126
+ if (needsDataAlias) {
127
+ names[DATA_ALIAS] = "data";
128
+ }
129
+ if (actions.tag !== void 0) {
130
+ names["#tag"] = "tag";
131
+ values[":tagVal"] = actions.tag;
132
+ setClauses.push("#tag = :tagVal");
133
+ }
134
+ if (actions.ttl !== void 0) {
135
+ names["#ttl"] = "ttl";
136
+ if (actions.ttl === null) {
137
+ removeClauses.push("#ttl");
138
+ } else {
139
+ values[":ttlVal"] = actions.ttl;
140
+ setClauses.push("#ttl = :ttlVal");
141
+ }
142
+ }
143
+ const parts = [];
144
+ if (setClauses.length) parts.push(`SET ${setClauses.join(", ")}`);
145
+ if (removeClauses.length) parts.push(`REMOVE ${removeClauses.join(", ")}`);
146
+ if (!parts.length) return;
147
+ const request = {
148
+ TableName: tableName,
149
+ Key: marshallKey(key),
150
+ UpdateExpression: parts.join(" "),
151
+ ExpressionAttributeNames: names,
152
+ ...Object.keys(values).length ? { ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }) } : {}
153
+ };
154
+ try {
155
+ await getClient().updateItem(request);
156
+ } catch (err) {
157
+ if (needsDataAlias && err.name === "ValidationException") {
158
+ const dataMap = {};
159
+ if (actions.set) Object.assign(dataMap, actions.set);
160
+ if (actions.increment) Object.assign(dataMap, actions.increment);
161
+ if (actions.append) Object.assign(dataMap, actions.append);
162
+ const retryNames = { "#data": "data" };
163
+ const retryValues = { ":fullData": dataMap };
164
+ const retrySets = ["#data = :fullData"];
165
+ if (actions.tag !== void 0) {
166
+ retryNames["#tag"] = "tag";
167
+ retryValues[":tagVal"] = actions.tag;
168
+ retrySets.push("#tag = :tagVal");
169
+ }
170
+ if (actions.ttl !== void 0 && actions.ttl !== null) {
171
+ retryNames["#ttl"] = "ttl";
172
+ retryValues[":ttlVal"] = actions.ttl;
173
+ retrySets.push("#ttl = :ttlVal");
174
+ }
175
+ const retryParts = [`SET ${retrySets.join(", ")}`];
176
+ if (actions.ttl === null) {
177
+ retryNames["#ttl"] = "ttl";
178
+ retryParts.push("REMOVE #ttl");
179
+ }
180
+ await getClient().updateItem({
181
+ TableName: tableName,
182
+ Key: marshallKey(key),
183
+ UpdateExpression: retryParts.join(" "),
184
+ ExpressionAttributeNames: retryNames,
185
+ ExpressionAttributeValues: marshall(retryValues, { removeUndefinedValues: true })
186
+ });
187
+ } else {
188
+ throw err;
189
+ }
190
+ }
191
+ },
192
+ async query(params) {
193
+ const names = { "#pk": "pk" };
194
+ const values = { ":pk": params.pk };
195
+ let keyCondition = "#pk = :pk";
196
+ if (params.sk !== void 0) {
197
+ const skCond = buildSkCondition(params.sk);
198
+ keyCondition += ` ${skCond.expression}`;
199
+ Object.assign(names, skCond.names);
200
+ Object.assign(values, skCond.values);
201
+ }
202
+ const result = await getClient().query({
203
+ TableName: tableName,
204
+ KeyConditionExpression: keyCondition,
205
+ ExpressionAttributeNames: names,
206
+ ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
207
+ ...params.limit ? { Limit: params.limit } : {},
208
+ ...params.scanIndexForward !== void 0 ? { ScanIndexForward: params.scanIndexForward } : {}
209
+ });
210
+ return (result.Items ?? []).map((item) => unmarshall(item));
211
+ },
212
+ async queryByTag(params) {
213
+ const names = { "#tag": "tag" };
214
+ const values = { ":tag": params.tag };
215
+ let keyCondition = "#tag = :tag";
216
+ if (params.pk !== void 0) {
217
+ const pkCond = buildSkCondition(params.pk);
218
+ const remapped = pkCond.expression.replace(/#sk/g, "#pk").replace(/:sk/g, ":pk");
219
+ keyCondition += ` ${remapped}`;
220
+ for (const [k, v] of Object.entries(pkCond.names)) {
221
+ names[k === "#sk" ? "#pk" : k] = v === "sk" ? "pk" : v;
222
+ }
223
+ for (const [k, v] of Object.entries(pkCond.values)) {
224
+ values[k.replace(":sk", ":pk")] = v;
225
+ }
226
+ }
227
+ const result = await getClient().query({
228
+ TableName: tableName,
229
+ IndexName: GSI_TAG_PK,
230
+ KeyConditionExpression: keyCondition,
231
+ ExpressionAttributeNames: names,
232
+ ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
233
+ ...params.limit ? { Limit: params.limit } : {},
234
+ ...params.scanIndexForward !== void 0 ? { ScanIndexForward: params.scanIndexForward } : {}
235
+ });
236
+ return (result.Items ?? []).map((item) => unmarshall(item));
237
+ }
238
+ };
239
+ };
240
+
241
+ export {
242
+ createTableClient
243
+ };
@@ -0,0 +1,6 @@
1
+ // src/runtime/lazy-import.ts
2
+ var lazyImport = (pkg) => import(pkg);
3
+
4
+ export {
5
+ lazyImport
6
+ };
@@ -0,0 +1,159 @@
1
+ import {
2
+ lazyImport
3
+ } from "./chunk-U56MLLWP.js";
4
+
5
+ // src/runtime/bucket-client.ts
6
+ var loadSdk = () => lazyImport("@aws-sdk/client-s3");
7
+ var createBucketMethods = (getClient, bucketName) => ({
8
+ bucketName,
9
+ async put(key, body, options) {
10
+ await getClient().putObject({
11
+ Bucket: bucketName,
12
+ Key: key,
13
+ Body: typeof body === "string" ? Buffer.from(body) : body,
14
+ ...options?.contentType ? { ContentType: options.contentType } : {}
15
+ });
16
+ },
17
+ async get(key) {
18
+ try {
19
+ const result = await getClient().getObject({
20
+ Bucket: bucketName,
21
+ Key: key
22
+ });
23
+ const chunks = [];
24
+ const stream = result.Body;
25
+ for await (const chunk of stream) {
26
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
27
+ }
28
+ return {
29
+ body: Buffer.concat(chunks),
30
+ contentType: result.ContentType
31
+ };
32
+ } catch (error) {
33
+ if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
34
+ return void 0;
35
+ }
36
+ throw error;
37
+ }
38
+ },
39
+ async delete(key) {
40
+ await getClient().deleteObject({
41
+ Bucket: bucketName,
42
+ Key: key
43
+ });
44
+ },
45
+ async list(prefix) {
46
+ const items = [];
47
+ let continuationToken;
48
+ do {
49
+ const result = await getClient().listObjectsV2({
50
+ Bucket: bucketName,
51
+ ...prefix ? { Prefix: prefix } : {},
52
+ ...continuationToken ? { ContinuationToken: continuationToken } : {}
53
+ });
54
+ for (const obj of result.Contents ?? []) {
55
+ if (obj.Key) {
56
+ items.push({
57
+ key: obj.Key,
58
+ size: obj.Size ?? 0,
59
+ lastModified: obj.LastModified
60
+ });
61
+ }
62
+ }
63
+ continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
64
+ } while (continuationToken);
65
+ return items;
66
+ }
67
+ });
68
+ var createEntityClient = (getClient, bucketName, entityName, cacheSeconds) => {
69
+ const entityKey = (id) => `${entityName}/${id}.json`;
70
+ return {
71
+ async put(id, data) {
72
+ await getClient().putObject({
73
+ Bucket: bucketName,
74
+ Key: entityKey(id),
75
+ Body: JSON.stringify(data),
76
+ ContentType: "application/json",
77
+ ...cacheSeconds != null ? { CacheControl: `public, max-age=${cacheSeconds}` } : {}
78
+ });
79
+ },
80
+ async get(id) {
81
+ try {
82
+ const result = await getClient().getObject({
83
+ Bucket: bucketName,
84
+ Key: entityKey(id)
85
+ });
86
+ const chunks = [];
87
+ const stream = result.Body;
88
+ for await (const chunk of stream) {
89
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
90
+ }
91
+ return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
92
+ } catch (error) {
93
+ if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
94
+ return void 0;
95
+ }
96
+ throw error;
97
+ }
98
+ },
99
+ async delete(id) {
100
+ await getClient().deleteObject({
101
+ Bucket: bucketName,
102
+ Key: entityKey(id)
103
+ });
104
+ },
105
+ async list() {
106
+ const items = [];
107
+ let continuationToken;
108
+ const prefix = `${entityName}/`;
109
+ do {
110
+ const result = await getClient().listObjectsV2({
111
+ Bucket: bucketName,
112
+ Prefix: prefix,
113
+ ...continuationToken ? { ContinuationToken: continuationToken } : {}
114
+ });
115
+ for (const obj of result.Contents ?? []) {
116
+ if (!obj.Key || !obj.Key.endsWith(".json")) continue;
117
+ const id = obj.Key.slice(prefix.length, -".json".length);
118
+ try {
119
+ const getResult = await getClient().getObject({
120
+ Bucket: bucketName,
121
+ Key: obj.Key
122
+ });
123
+ const chunks = [];
124
+ const stream = getResult.Body;
125
+ for await (const chunk of stream) {
126
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
127
+ }
128
+ items.push({ id, data: JSON.parse(Buffer.concat(chunks).toString("utf-8")) });
129
+ } catch {
130
+ }
131
+ }
132
+ continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
133
+ } while (continuationToken);
134
+ return items;
135
+ }
136
+ };
137
+ };
138
+ var createBucketClient = async (bucketName) => {
139
+ const { S3: S3Client } = await loadSdk();
140
+ let client = null;
141
+ const getClient = () => client ??= new S3Client({});
142
+ return createBucketMethods(getClient, bucketName);
143
+ };
144
+ var createBucketClientWithEntities = async (bucketName, entitiesConfig) => {
145
+ const { S3: S3Client } = await loadSdk();
146
+ let client = null;
147
+ const getClient = () => client ??= new S3Client({});
148
+ const base = createBucketMethods(getClient, bucketName);
149
+ const result = { ...base };
150
+ for (const [entityName, config] of Object.entries(entitiesConfig)) {
151
+ result[entityName] = createEntityClient(getClient, bucketName, entityName, config.cacheSeconds);
152
+ }
153
+ return result;
154
+ };
155
+
156
+ export {
157
+ createBucketClient,
158
+ createBucketClientWithEntities
159
+ };
@@ -0,0 +1,16 @@
1
+ // src/handlers/handler-options.ts
2
+ var toSeconds = (d) => {
3
+ if (typeof d === "number") return d;
4
+ const match = d.match(/^(\d+(?:\.\d+)?)(s|m|h|d)$/);
5
+ if (!match) throw new Error(`Invalid duration: "${d}"`);
6
+ const n = Number(match[1]);
7
+ const unit = match[2];
8
+ if (unit === "d") return n * 86400;
9
+ if (unit === "h") return n * 3600;
10
+ if (unit === "m") return n * 60;
11
+ return n;
12
+ };
13
+
14
+ export {
15
+ toSeconds
16
+ };
@@ -0,0 +1,34 @@
1
+ import {
2
+ lazyImport
3
+ } from "./chunk-U56MLLWP.js";
4
+
5
+ // src/runtime/email-client.ts
6
+ var loadSdk = () => lazyImport("@aws-sdk/client-sesv2");
7
+ var createEmailClient = async () => {
8
+ const { SESv2Client: Cls, SendEmailCommand } = await loadSdk();
9
+ let client = null;
10
+ const getClient = () => client ??= new Cls({});
11
+ return {
12
+ async send({ from, to, subject, html, text }) {
13
+ const toAddresses = Array.isArray(to) ? to : [to];
14
+ await getClient().send(
15
+ new SendEmailCommand({
16
+ FromEmailAddress: from,
17
+ Destination: { ToAddresses: toAddresses },
18
+ Content: {
19
+ Simple: {
20
+ Subject: { Data: subject },
21
+ Body: {
22
+ ...html ? { Html: { Data: html } } : {},
23
+ ...text ? { Text: { Data: text } } : {}
24
+ }
25
+ }
26
+ }
27
+ })
28
+ );
29
+ }
30
+ };
31
+ };
32
+ export {
33
+ createEmailClient
34
+ };