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.
@@ -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,4 +1,5 @@
1
1
  export * from "./domain";
2
2
  export * from "./literals";
3
+ export { copyTable } from "./copy-table";
3
4
  export type { Key } from "./types";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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 = {};
@@ -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.53",
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",