qstd 0.3.53 → 0.3.54
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/server/aws/ddb/copy-table.d.ts +51 -0
- package/dist/server/aws/ddb/copy-table.d.ts.map +1 -0
- package/dist/server/aws/ddb/index.d.ts +1 -0
- package/dist/server/aws/ddb/index.d.ts.map +1 -1
- package/dist/server/index.cjs +229 -0
- package/dist/server/index.js +229 -1
- package/package.json +4 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
type CopyTableProps = {
|
|
3
|
+
/** Source table configuration */
|
|
4
|
+
source: {
|
|
5
|
+
tableName: string;
|
|
6
|
+
/** Optional AWS config for cross-region copying */
|
|
7
|
+
config?: DynamoDBClientConfig;
|
|
8
|
+
};
|
|
9
|
+
/** Destination table configuration */
|
|
10
|
+
destination: {
|
|
11
|
+
tableName: string;
|
|
12
|
+
/** Optional AWS config for cross-region copying */
|
|
13
|
+
config?: DynamoDBClientConfig;
|
|
14
|
+
};
|
|
15
|
+
/** Whether to create destination table if it doesn't exist */
|
|
16
|
+
create?: boolean;
|
|
17
|
+
/** If true, only copy schema without data */
|
|
18
|
+
schemaOnly?: boolean;
|
|
19
|
+
/** Whether to enable continuous backups on destination table */
|
|
20
|
+
continuousBackups?: boolean;
|
|
21
|
+
/** Optional function to transform each item during copy */
|
|
22
|
+
transform?: (item: Record<string, unknown>, index: number) => Record<string, unknown>;
|
|
23
|
+
/** Whether to show progress logs */
|
|
24
|
+
log?: boolean;
|
|
25
|
+
};
|
|
26
|
+
type CopyTableResult = {
|
|
27
|
+
/** Number of items copied */
|
|
28
|
+
count: number;
|
|
29
|
+
/** Operation status */
|
|
30
|
+
status: "SUCCESS" | "FAIL";
|
|
31
|
+
/** Whether only schema was copied */
|
|
32
|
+
schemaOnly?: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Copy a DynamoDB table to another table, with support for cross-region copying,
|
|
36
|
+
* schema-only copying, and data transformation.
|
|
37
|
+
*
|
|
38
|
+
* This function can:
|
|
39
|
+
* - Copy data between tables in the same or different regions
|
|
40
|
+
* - Create the destination table if it doesn't exist
|
|
41
|
+
* - Copy only the schema without data
|
|
42
|
+
* - Transform items during the copy process
|
|
43
|
+
* - Enable continuous backups on the destination table
|
|
44
|
+
* - Handle large tables with proper pagination and retry logic
|
|
45
|
+
*
|
|
46
|
+
* @param props Configuration options for the copy operation
|
|
47
|
+
* @returns Promise resolving to copy results
|
|
48
|
+
*/
|
|
49
|
+
export declare const copyTable: (props: CopyTableProps) => Promise<CopyTableResult>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=copy-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-table.d.ts","sourceRoot":"","sources":["../../../../src/server/aws/ddb/copy-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,oBAAoB,EAG1B,MAAM,0BAA0B,CAAC;AAqKlC,KAAK,cAAc,GAAG;IACpB,iCAAiC;IACjC,MAAM,EAAE;QACN,SAAS,EAAE,MAAM,CAAC;QAClB,mDAAmD;QACnD,MAAM,CAAC,EAAE,oBAAoB,CAAC;KAC/B,CAAC;IACF,sCAAsC;IACtC,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,mDAAmD;QACnD,MAAM,CAAC,EAAE,oBAAoB,CAAC;KAC/B,CAAC;IACF,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,CACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,oCAAoC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,OAAO,cAAc,KACpB,OAAO,CAAC,eAAe,CAsMzB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/aws/ddb/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/aws/ddb/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/server/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var fs = require('fs');
|
|
|
6
6
|
var arktype = require('arktype');
|
|
7
7
|
var libDynamodb = require('@aws-sdk/lib-dynamodb');
|
|
8
8
|
var clientDynamodb = require('@aws-sdk/client-dynamodb');
|
|
9
|
+
var signale = require('signale');
|
|
9
10
|
var clientSns = require('@aws-sdk/client-sns');
|
|
10
11
|
var clientSqs = require('@aws-sdk/client-sqs');
|
|
11
12
|
var clientSes = require('@aws-sdk/client-ses');
|
|
@@ -17,6 +18,7 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
|
|
|
17
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
19
|
|
|
19
20
|
var awaitSpawn__default = /*#__PURE__*/_interopDefault(awaitSpawn);
|
|
21
|
+
var signale__default = /*#__PURE__*/_interopDefault(signale);
|
|
20
22
|
|
|
21
23
|
var __defProp = Object.defineProperty;
|
|
22
24
|
var __export = (target, all) => {
|
|
@@ -1060,6 +1062,7 @@ __export(ddb_exports, {
|
|
|
1060
1062
|
batchDelete: () => batchDelete,
|
|
1061
1063
|
batchGet: () => batchGet,
|
|
1062
1064
|
batchWrite: () => batchWrite,
|
|
1065
|
+
copyTable: () => copyTable,
|
|
1063
1066
|
create: () => create2,
|
|
1064
1067
|
deleteTable: () => deleteTable,
|
|
1065
1068
|
find: () => find,
|
|
@@ -1659,6 +1662,232 @@ var lsiPhash = {
|
|
|
1659
1662
|
name: "phash-lsi",
|
|
1660
1663
|
sk: "phash"
|
|
1661
1664
|
};
|
|
1665
|
+
var validateTableName = (tableName) => {
|
|
1666
|
+
const regex = /^[a-zA-Z0-9_.-]{3,255}$/;
|
|
1667
|
+
if (!regex.test(tableName)) {
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
`tableName must follow AWS naming rules (3-255 length, and only the following characters: a-z, A-Z, 0-9, _-.) but received: ${tableName}`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
var clearTableSchema = (table) => {
|
|
1674
|
+
const cleanTable = {
|
|
1675
|
+
TableName: table.TableName,
|
|
1676
|
+
AttributeDefinitions: table.AttributeDefinitions,
|
|
1677
|
+
KeySchema: table.KeySchema
|
|
1678
|
+
};
|
|
1679
|
+
if (table.BillingModeSummary?.BillingMode) {
|
|
1680
|
+
cleanTable.BillingMode = table.BillingModeSummary.BillingMode;
|
|
1681
|
+
}
|
|
1682
|
+
if (table.ProvisionedThroughput && table.BillingModeSummary?.BillingMode !== "PAY_PER_REQUEST") {
|
|
1683
|
+
cleanTable.ProvisionedThroughput = {
|
|
1684
|
+
ReadCapacityUnits: table.ProvisionedThroughput.ReadCapacityUnits,
|
|
1685
|
+
WriteCapacityUnits: table.ProvisionedThroughput.WriteCapacityUnits
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
if (table.LocalSecondaryIndexes && table.LocalSecondaryIndexes.length > 0) {
|
|
1689
|
+
cleanTable.LocalSecondaryIndexes = table.LocalSecondaryIndexes.map(
|
|
1690
|
+
(lsi3) => ({
|
|
1691
|
+
IndexName: lsi3.IndexName,
|
|
1692
|
+
KeySchema: lsi3.KeySchema,
|
|
1693
|
+
Projection: lsi3.Projection
|
|
1694
|
+
})
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
|
|
1698
|
+
cleanTable.GlobalSecondaryIndexes = table.GlobalSecondaryIndexes.map(
|
|
1699
|
+
(gsi) => {
|
|
1700
|
+
const cleanGsi = {
|
|
1701
|
+
IndexName: gsi.IndexName,
|
|
1702
|
+
KeySchema: gsi.KeySchema,
|
|
1703
|
+
Projection: gsi.Projection
|
|
1704
|
+
};
|
|
1705
|
+
if (gsi.ProvisionedThroughput && table.BillingModeSummary?.BillingMode !== "PAY_PER_REQUEST") {
|
|
1706
|
+
cleanGsi.ProvisionedThroughput = {
|
|
1707
|
+
ReadCapacityUnits: gsi.ProvisionedThroughput.ReadCapacityUnits,
|
|
1708
|
+
WriteCapacityUnits: gsi.ProvisionedThroughput.WriteCapacityUnits
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
return cleanGsi;
|
|
1712
|
+
}
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
if (table.SSEDescription) {
|
|
1716
|
+
cleanTable.SSESpecification = {
|
|
1717
|
+
Enabled: table.SSEDescription.Status === "ENABLED" || table.SSEDescription.Status === "ENABLING"
|
|
1718
|
+
};
|
|
1719
|
+
if (table.SSEDescription.SSEType === "KMS" && table.SSEDescription.KMSMasterKeyArn) {
|
|
1720
|
+
cleanTable.SSESpecification.SSEType = "KMS";
|
|
1721
|
+
cleanTable.SSESpecification.KMSMasterKeyId = table.SSEDescription.KMSMasterKeyArn;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
if (table.StreamSpecification) {
|
|
1725
|
+
cleanTable.StreamSpecification = {
|
|
1726
|
+
StreamEnabled: table.StreamSpecification.StreamEnabled,
|
|
1727
|
+
StreamViewType: table.StreamSpecification.StreamViewType
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
return cleanTable;
|
|
1731
|
+
};
|
|
1732
|
+
var waitForActive = async (tableName, client, log2) => {
|
|
1733
|
+
if (log2) {
|
|
1734
|
+
signale__default.default.log(`Waiting for table "${tableName}" to become active...`);
|
|
1735
|
+
}
|
|
1736
|
+
await clientDynamodb.waitUntilTableExists(
|
|
1737
|
+
{
|
|
1738
|
+
client,
|
|
1739
|
+
// Check every 5 seconds
|
|
1740
|
+
minDelay: 5,
|
|
1741
|
+
maxDelay: 5,
|
|
1742
|
+
// Wait up to 10 minutes
|
|
1743
|
+
maxWaitTime: 600
|
|
1744
|
+
},
|
|
1745
|
+
{ TableName: tableName }
|
|
1746
|
+
);
|
|
1747
|
+
if (log2) signale__default.default.success(`Table "${tableName}" is now active!`);
|
|
1748
|
+
};
|
|
1749
|
+
var sleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1750
|
+
var copyTable = async (props) => {
|
|
1751
|
+
try {
|
|
1752
|
+
validateTableName(props.source.tableName);
|
|
1753
|
+
validateTableName(props.destination.tableName);
|
|
1754
|
+
const sourceDynamoClient = new clientDynamodb.DynamoDBClient({
|
|
1755
|
+
...props.source.config,
|
|
1756
|
+
region: props.source.config?.region || "us-east-1"
|
|
1757
|
+
});
|
|
1758
|
+
const sourceDocClient = libDynamodb.DynamoDBDocumentClient.from(sourceDynamoClient);
|
|
1759
|
+
const destDynamoClient = new clientDynamodb.DynamoDBClient({
|
|
1760
|
+
...props.destination.config,
|
|
1761
|
+
region: props.destination.config?.region || "us-east-1"
|
|
1762
|
+
});
|
|
1763
|
+
const destDocClient = libDynamodb.DynamoDBDocumentClient.from(destDynamoClient);
|
|
1764
|
+
let counter = 0;
|
|
1765
|
+
if (props.create) {
|
|
1766
|
+
try {
|
|
1767
|
+
const sourceCommand = new clientDynamodb.DescribeTableCommand({
|
|
1768
|
+
TableName: props.source.tableName
|
|
1769
|
+
});
|
|
1770
|
+
const sourceResponse = await sourceDynamoClient.send(sourceCommand);
|
|
1771
|
+
if (sourceResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1772
|
+
throw new Error("Source table is not active");
|
|
1773
|
+
}
|
|
1774
|
+
const cleanedTable = clearTableSchema(sourceResponse.Table);
|
|
1775
|
+
cleanedTable.TableName = props.destination.tableName;
|
|
1776
|
+
try {
|
|
1777
|
+
const createCommand = new clientDynamodb.CreateTableCommand(cleanedTable);
|
|
1778
|
+
await destDynamoClient.send(createCommand);
|
|
1779
|
+
} catch (error2) {
|
|
1780
|
+
if (error2 instanceof Error && error2.name !== "ResourceInUseException") {
|
|
1781
|
+
throw error2;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
await waitForActive(
|
|
1785
|
+
props.destination.tableName,
|
|
1786
|
+
destDynamoClient,
|
|
1787
|
+
props.log
|
|
1788
|
+
);
|
|
1789
|
+
if (props.schemaOnly) {
|
|
1790
|
+
return { count: 0, status: "SUCCESS", schemaOnly: true };
|
|
1791
|
+
}
|
|
1792
|
+
if (props.continuousBackups) {
|
|
1793
|
+
try {
|
|
1794
|
+
const sourceBackupsCommand = new clientDynamodb.DescribeContinuousBackupsCommand({
|
|
1795
|
+
TableName: props.source.tableName
|
|
1796
|
+
});
|
|
1797
|
+
const sourceBackupsResponse = await sourceDynamoClient.send(
|
|
1798
|
+
sourceBackupsCommand
|
|
1799
|
+
);
|
|
1800
|
+
if (sourceBackupsResponse.ContinuousBackupsDescription?.ContinuousBackupsStatus === "ENABLED") {
|
|
1801
|
+
const updateBackupsCommand = new clientDynamodb.UpdateContinuousBackupsCommand({
|
|
1802
|
+
TableName: props.destination.tableName,
|
|
1803
|
+
PointInTimeRecoverySpecification: {
|
|
1804
|
+
PointInTimeRecoveryEnabled: true
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
await destDynamoClient.send(updateBackupsCommand);
|
|
1808
|
+
}
|
|
1809
|
+
} catch (error2) {
|
|
1810
|
+
console.warn("Failed to enable continuous backups:", error2);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
} catch (error2) {
|
|
1814
|
+
signale__default.default.error({ error: error2 });
|
|
1815
|
+
return { count: 0, status: "FAIL" };
|
|
1816
|
+
}
|
|
1817
|
+
} else {
|
|
1818
|
+
try {
|
|
1819
|
+
const sourceCommand = new clientDynamodb.DescribeTableCommand({
|
|
1820
|
+
TableName: props.source.tableName
|
|
1821
|
+
});
|
|
1822
|
+
const sourceResponse = await sourceDynamoClient.send(sourceCommand);
|
|
1823
|
+
if (sourceResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1824
|
+
throw new Error("Source table is not active");
|
|
1825
|
+
}
|
|
1826
|
+
const destCommand = new clientDynamodb.DescribeTableCommand({
|
|
1827
|
+
TableName: props.destination.tableName
|
|
1828
|
+
});
|
|
1829
|
+
const destResponse = await destDynamoClient.send(destCommand);
|
|
1830
|
+
if (destResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1831
|
+
throw new Error("Destination table is not active");
|
|
1832
|
+
}
|
|
1833
|
+
} catch (error2) {
|
|
1834
|
+
signale__default.default.error({ error: error2 });
|
|
1835
|
+
return { count: 0, status: "FAIL" };
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
let lastEvaluatedKey = void 0;
|
|
1839
|
+
while (true) {
|
|
1840
|
+
const scanInput = {
|
|
1841
|
+
TableName: props.source.tableName,
|
|
1842
|
+
Limit: 25,
|
|
1843
|
+
// Process 25 items at a time
|
|
1844
|
+
ExclusiveStartKey: lastEvaluatedKey
|
|
1845
|
+
};
|
|
1846
|
+
const scanCommand = new libDynamodb.ScanCommand(scanInput);
|
|
1847
|
+
const scanResponse = await sourceDocClient.send(
|
|
1848
|
+
scanCommand
|
|
1849
|
+
);
|
|
1850
|
+
const items = scanResponse.Items || [];
|
|
1851
|
+
if (items.length === 0) {
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1854
|
+
const writeRequests = items.map(
|
|
1855
|
+
(item2, index) => ({
|
|
1856
|
+
PutRequest: {
|
|
1857
|
+
Item: props.transform ? props.transform(item2, index) : item2
|
|
1858
|
+
}
|
|
1859
|
+
})
|
|
1860
|
+
);
|
|
1861
|
+
let retries = 0;
|
|
1862
|
+
let unprocessedItems = writeRequests;
|
|
1863
|
+
while (unprocessedItems.length > 0) {
|
|
1864
|
+
const batchWriteCommand = new libDynamodb.BatchWriteCommand({
|
|
1865
|
+
RequestItems: {
|
|
1866
|
+
[props.destination.tableName]: unprocessedItems
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
const batchResponse = await destDocClient.send(batchWriteCommand);
|
|
1870
|
+
const stillUnprocessed = batchResponse.UnprocessedItems?.[props.destination.tableName] || [];
|
|
1871
|
+
counter += unprocessedItems.length - stillUnprocessed.length;
|
|
1872
|
+
if (stillUnprocessed.length > 0) {
|
|
1873
|
+
retries++;
|
|
1874
|
+
unprocessedItems = stillUnprocessed;
|
|
1875
|
+
await sleep4(2 * retries * 100);
|
|
1876
|
+
} else {
|
|
1877
|
+
unprocessedItems = [];
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
process.stdout.write(`\rCopied ${counter} items`);
|
|
1881
|
+
lastEvaluatedKey = scanResponse.LastEvaluatedKey;
|
|
1882
|
+
if (!lastEvaluatedKey) break;
|
|
1883
|
+
}
|
|
1884
|
+
process.stdout.write("\n");
|
|
1885
|
+
return { count: counter, status: "SUCCESS" };
|
|
1886
|
+
} catch (error2) {
|
|
1887
|
+
console.error("Copy operation failed:", error2);
|
|
1888
|
+
return { count: 0, status: "FAIL" };
|
|
1889
|
+
}
|
|
1890
|
+
};
|
|
1662
1891
|
|
|
1663
1892
|
// src/server/aws/sns/index.ts
|
|
1664
1893
|
var sns_exports = {};
|
package/dist/server/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import awaitSpawn from 'await-spawn';
|
|
|
3
3
|
import { promises } from 'fs';
|
|
4
4
|
import { ArkErrors } from 'arktype';
|
|
5
5
|
import { DynamoDBDocumentClient, ScanCommand, QueryCommand, DeleteCommand, PutCommand, BatchGetCommand, TransactWriteCommand, BatchWriteCommand } from '@aws-sdk/lib-dynamodb';
|
|
6
|
-
import { DynamoDBClient, DeleteTableCommand, DescribeTableCommand, DynamoDBServiceException } from '@aws-sdk/client-dynamodb';
|
|
6
|
+
import { DynamoDBClient, DeleteTableCommand, DescribeTableCommand, DynamoDBServiceException, CreateTableCommand, DescribeContinuousBackupsCommand, UpdateContinuousBackupsCommand, waitUntilTableExists } from '@aws-sdk/client-dynamodb';
|
|
7
|
+
import signale from 'signale';
|
|
7
8
|
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
|
|
8
9
|
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
|
|
9
10
|
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
@@ -1053,6 +1054,7 @@ __export(ddb_exports, {
|
|
|
1053
1054
|
batchDelete: () => batchDelete,
|
|
1054
1055
|
batchGet: () => batchGet,
|
|
1055
1056
|
batchWrite: () => batchWrite,
|
|
1057
|
+
copyTable: () => copyTable,
|
|
1056
1058
|
create: () => create2,
|
|
1057
1059
|
deleteTable: () => deleteTable,
|
|
1058
1060
|
find: () => find,
|
|
@@ -1652,6 +1654,232 @@ var lsiPhash = {
|
|
|
1652
1654
|
name: "phash-lsi",
|
|
1653
1655
|
sk: "phash"
|
|
1654
1656
|
};
|
|
1657
|
+
var validateTableName = (tableName) => {
|
|
1658
|
+
const regex = /^[a-zA-Z0-9_.-]{3,255}$/;
|
|
1659
|
+
if (!regex.test(tableName)) {
|
|
1660
|
+
throw new Error(
|
|
1661
|
+
`tableName must follow AWS naming rules (3-255 length, and only the following characters: a-z, A-Z, 0-9, _-.) but received: ${tableName}`
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
var clearTableSchema = (table) => {
|
|
1666
|
+
const cleanTable = {
|
|
1667
|
+
TableName: table.TableName,
|
|
1668
|
+
AttributeDefinitions: table.AttributeDefinitions,
|
|
1669
|
+
KeySchema: table.KeySchema
|
|
1670
|
+
};
|
|
1671
|
+
if (table.BillingModeSummary?.BillingMode) {
|
|
1672
|
+
cleanTable.BillingMode = table.BillingModeSummary.BillingMode;
|
|
1673
|
+
}
|
|
1674
|
+
if (table.ProvisionedThroughput && table.BillingModeSummary?.BillingMode !== "PAY_PER_REQUEST") {
|
|
1675
|
+
cleanTable.ProvisionedThroughput = {
|
|
1676
|
+
ReadCapacityUnits: table.ProvisionedThroughput.ReadCapacityUnits,
|
|
1677
|
+
WriteCapacityUnits: table.ProvisionedThroughput.WriteCapacityUnits
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
if (table.LocalSecondaryIndexes && table.LocalSecondaryIndexes.length > 0) {
|
|
1681
|
+
cleanTable.LocalSecondaryIndexes = table.LocalSecondaryIndexes.map(
|
|
1682
|
+
(lsi3) => ({
|
|
1683
|
+
IndexName: lsi3.IndexName,
|
|
1684
|
+
KeySchema: lsi3.KeySchema,
|
|
1685
|
+
Projection: lsi3.Projection
|
|
1686
|
+
})
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
|
|
1690
|
+
cleanTable.GlobalSecondaryIndexes = table.GlobalSecondaryIndexes.map(
|
|
1691
|
+
(gsi) => {
|
|
1692
|
+
const cleanGsi = {
|
|
1693
|
+
IndexName: gsi.IndexName,
|
|
1694
|
+
KeySchema: gsi.KeySchema,
|
|
1695
|
+
Projection: gsi.Projection
|
|
1696
|
+
};
|
|
1697
|
+
if (gsi.ProvisionedThroughput && table.BillingModeSummary?.BillingMode !== "PAY_PER_REQUEST") {
|
|
1698
|
+
cleanGsi.ProvisionedThroughput = {
|
|
1699
|
+
ReadCapacityUnits: gsi.ProvisionedThroughput.ReadCapacityUnits,
|
|
1700
|
+
WriteCapacityUnits: gsi.ProvisionedThroughput.WriteCapacityUnits
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
return cleanGsi;
|
|
1704
|
+
}
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
if (table.SSEDescription) {
|
|
1708
|
+
cleanTable.SSESpecification = {
|
|
1709
|
+
Enabled: table.SSEDescription.Status === "ENABLED" || table.SSEDescription.Status === "ENABLING"
|
|
1710
|
+
};
|
|
1711
|
+
if (table.SSEDescription.SSEType === "KMS" && table.SSEDescription.KMSMasterKeyArn) {
|
|
1712
|
+
cleanTable.SSESpecification.SSEType = "KMS";
|
|
1713
|
+
cleanTable.SSESpecification.KMSMasterKeyId = table.SSEDescription.KMSMasterKeyArn;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (table.StreamSpecification) {
|
|
1717
|
+
cleanTable.StreamSpecification = {
|
|
1718
|
+
StreamEnabled: table.StreamSpecification.StreamEnabled,
|
|
1719
|
+
StreamViewType: table.StreamSpecification.StreamViewType
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
return cleanTable;
|
|
1723
|
+
};
|
|
1724
|
+
var waitForActive = async (tableName, client, log2) => {
|
|
1725
|
+
if (log2) {
|
|
1726
|
+
signale.log(`Waiting for table "${tableName}" to become active...`);
|
|
1727
|
+
}
|
|
1728
|
+
await waitUntilTableExists(
|
|
1729
|
+
{
|
|
1730
|
+
client,
|
|
1731
|
+
// Check every 5 seconds
|
|
1732
|
+
minDelay: 5,
|
|
1733
|
+
maxDelay: 5,
|
|
1734
|
+
// Wait up to 10 minutes
|
|
1735
|
+
maxWaitTime: 600
|
|
1736
|
+
},
|
|
1737
|
+
{ TableName: tableName }
|
|
1738
|
+
);
|
|
1739
|
+
if (log2) signale.success(`Table "${tableName}" is now active!`);
|
|
1740
|
+
};
|
|
1741
|
+
var sleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1742
|
+
var copyTable = async (props) => {
|
|
1743
|
+
try {
|
|
1744
|
+
validateTableName(props.source.tableName);
|
|
1745
|
+
validateTableName(props.destination.tableName);
|
|
1746
|
+
const sourceDynamoClient = new DynamoDBClient({
|
|
1747
|
+
...props.source.config,
|
|
1748
|
+
region: props.source.config?.region || "us-east-1"
|
|
1749
|
+
});
|
|
1750
|
+
const sourceDocClient = DynamoDBDocumentClient.from(sourceDynamoClient);
|
|
1751
|
+
const destDynamoClient = new DynamoDBClient({
|
|
1752
|
+
...props.destination.config,
|
|
1753
|
+
region: props.destination.config?.region || "us-east-1"
|
|
1754
|
+
});
|
|
1755
|
+
const destDocClient = DynamoDBDocumentClient.from(destDynamoClient);
|
|
1756
|
+
let counter = 0;
|
|
1757
|
+
if (props.create) {
|
|
1758
|
+
try {
|
|
1759
|
+
const sourceCommand = new DescribeTableCommand({
|
|
1760
|
+
TableName: props.source.tableName
|
|
1761
|
+
});
|
|
1762
|
+
const sourceResponse = await sourceDynamoClient.send(sourceCommand);
|
|
1763
|
+
if (sourceResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1764
|
+
throw new Error("Source table is not active");
|
|
1765
|
+
}
|
|
1766
|
+
const cleanedTable = clearTableSchema(sourceResponse.Table);
|
|
1767
|
+
cleanedTable.TableName = props.destination.tableName;
|
|
1768
|
+
try {
|
|
1769
|
+
const createCommand = new CreateTableCommand(cleanedTable);
|
|
1770
|
+
await destDynamoClient.send(createCommand);
|
|
1771
|
+
} catch (error2) {
|
|
1772
|
+
if (error2 instanceof Error && error2.name !== "ResourceInUseException") {
|
|
1773
|
+
throw error2;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
await waitForActive(
|
|
1777
|
+
props.destination.tableName,
|
|
1778
|
+
destDynamoClient,
|
|
1779
|
+
props.log
|
|
1780
|
+
);
|
|
1781
|
+
if (props.schemaOnly) {
|
|
1782
|
+
return { count: 0, status: "SUCCESS", schemaOnly: true };
|
|
1783
|
+
}
|
|
1784
|
+
if (props.continuousBackups) {
|
|
1785
|
+
try {
|
|
1786
|
+
const sourceBackupsCommand = new DescribeContinuousBackupsCommand({
|
|
1787
|
+
TableName: props.source.tableName
|
|
1788
|
+
});
|
|
1789
|
+
const sourceBackupsResponse = await sourceDynamoClient.send(
|
|
1790
|
+
sourceBackupsCommand
|
|
1791
|
+
);
|
|
1792
|
+
if (sourceBackupsResponse.ContinuousBackupsDescription?.ContinuousBackupsStatus === "ENABLED") {
|
|
1793
|
+
const updateBackupsCommand = new UpdateContinuousBackupsCommand({
|
|
1794
|
+
TableName: props.destination.tableName,
|
|
1795
|
+
PointInTimeRecoverySpecification: {
|
|
1796
|
+
PointInTimeRecoveryEnabled: true
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
await destDynamoClient.send(updateBackupsCommand);
|
|
1800
|
+
}
|
|
1801
|
+
} catch (error2) {
|
|
1802
|
+
console.warn("Failed to enable continuous backups:", error2);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
} catch (error2) {
|
|
1806
|
+
signale.error({ error: error2 });
|
|
1807
|
+
return { count: 0, status: "FAIL" };
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
try {
|
|
1811
|
+
const sourceCommand = new DescribeTableCommand({
|
|
1812
|
+
TableName: props.source.tableName
|
|
1813
|
+
});
|
|
1814
|
+
const sourceResponse = await sourceDynamoClient.send(sourceCommand);
|
|
1815
|
+
if (sourceResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1816
|
+
throw new Error("Source table is not active");
|
|
1817
|
+
}
|
|
1818
|
+
const destCommand = new DescribeTableCommand({
|
|
1819
|
+
TableName: props.destination.tableName
|
|
1820
|
+
});
|
|
1821
|
+
const destResponse = await destDynamoClient.send(destCommand);
|
|
1822
|
+
if (destResponse.Table?.TableStatus !== "ACTIVE") {
|
|
1823
|
+
throw new Error("Destination table is not active");
|
|
1824
|
+
}
|
|
1825
|
+
} catch (error2) {
|
|
1826
|
+
signale.error({ error: error2 });
|
|
1827
|
+
return { count: 0, status: "FAIL" };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
let lastEvaluatedKey = void 0;
|
|
1831
|
+
while (true) {
|
|
1832
|
+
const scanInput = {
|
|
1833
|
+
TableName: props.source.tableName,
|
|
1834
|
+
Limit: 25,
|
|
1835
|
+
// Process 25 items at a time
|
|
1836
|
+
ExclusiveStartKey: lastEvaluatedKey
|
|
1837
|
+
};
|
|
1838
|
+
const scanCommand = new ScanCommand(scanInput);
|
|
1839
|
+
const scanResponse = await sourceDocClient.send(
|
|
1840
|
+
scanCommand
|
|
1841
|
+
);
|
|
1842
|
+
const items = scanResponse.Items || [];
|
|
1843
|
+
if (items.length === 0) {
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
const writeRequests = items.map(
|
|
1847
|
+
(item2, index) => ({
|
|
1848
|
+
PutRequest: {
|
|
1849
|
+
Item: props.transform ? props.transform(item2, index) : item2
|
|
1850
|
+
}
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
let retries = 0;
|
|
1854
|
+
let unprocessedItems = writeRequests;
|
|
1855
|
+
while (unprocessedItems.length > 0) {
|
|
1856
|
+
const batchWriteCommand = new BatchWriteCommand({
|
|
1857
|
+
RequestItems: {
|
|
1858
|
+
[props.destination.tableName]: unprocessedItems
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
const batchResponse = await destDocClient.send(batchWriteCommand);
|
|
1862
|
+
const stillUnprocessed = batchResponse.UnprocessedItems?.[props.destination.tableName] || [];
|
|
1863
|
+
counter += unprocessedItems.length - stillUnprocessed.length;
|
|
1864
|
+
if (stillUnprocessed.length > 0) {
|
|
1865
|
+
retries++;
|
|
1866
|
+
unprocessedItems = stillUnprocessed;
|
|
1867
|
+
await sleep4(2 * retries * 100);
|
|
1868
|
+
} else {
|
|
1869
|
+
unprocessedItems = [];
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
process.stdout.write(`\rCopied ${counter} items`);
|
|
1873
|
+
lastEvaluatedKey = scanResponse.LastEvaluatedKey;
|
|
1874
|
+
if (!lastEvaluatedKey) break;
|
|
1875
|
+
}
|
|
1876
|
+
process.stdout.write("\n");
|
|
1877
|
+
return { count: counter, status: "SUCCESS" };
|
|
1878
|
+
} catch (error2) {
|
|
1879
|
+
console.error("Copy operation failed:", error2);
|
|
1880
|
+
return { count: 0, status: "FAIL" };
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1655
1883
|
|
|
1656
1884
|
// src/server/aws/sns/index.ts
|
|
1657
1885
|
var sns_exports = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qstd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.54",
|
|
4
4
|
"description": "Standard Block component and utilities library with Panda CSS",
|
|
5
5
|
"author": "malin1",
|
|
6
6
|
"license": "MIT",
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
"react-loader-spinner": "^6.1.6",
|
|
91
91
|
"react-spinners": "^0.17.0",
|
|
92
92
|
"use-immer": "^0.11.0",
|
|
93
|
-
"await-spawn": "^4.0.2"
|
|
93
|
+
"await-spawn": "^4.0.2",
|
|
94
|
+
"signale": "^1.4.0"
|
|
94
95
|
},
|
|
95
96
|
"devDependencies": {
|
|
96
97
|
"@eslint/js": "^9.39.1",
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
"@types/node": "^22.10.5",
|
|
102
103
|
"@types/react": "^19.1.12",
|
|
103
104
|
"@types/react-dom": "^19.1.8",
|
|
105
|
+
"@types/signale": "^1.4.7",
|
|
104
106
|
"eslint": "^9.39.1",
|
|
105
107
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
106
108
|
"globals": "^16.2.0",
|