{"_id":"@bindist/dynamo-to-pg","name":"@bindist/dynamo-to-pg","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@bindist/dynamo-to-pg","publishConfig":{"access":"public"},"version":"1.0.0","description":"Translate DynamoDB operations to PostgreSQL queries","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"scripts":{"build":"tsc","test":"vitest run","test:watch":"vitest"},"keywords":["dynamodb","postgresql","adapter"],"engines":{"node":">=20"},"license":"MIT","devDependencies":{"@aws-sdk/lib-dynamodb":"^3.0.0","@types/node":"^25.6.0","typescript":"^6.0.2","vitest":"^3.2.4"},"peerDependencies":{"@aws-sdk/lib-dynamodb":"^3.0.0"},"gitHead":"1a836f52a5f2d13d2d4ea5189ac02f0679771bb9","_id":"@bindist/dynamo-to-pg@1.0.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-ckiB0cQiAwsagL0Y3GhRsLF8nu/b2xFJwoiUYkUZ1gDXGLhORprQsq584WUh5AUaYxCX8mhMrknuR1KulJUuFA==","shasum":"1e4aa78e993a0366f456f85b5be7841c71653832","tarball":"https://registry.npmjs.org/@bindist/dynamo-to-pg/-/dynamo-to-pg-1.0.0.tgz","fileCount":9,"unpackedSize":36782,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHKn7fVTMrI2ocKYgfVlgz63fKPqrvVtCRvzczR8rQuNAiAWskODjZeEwp6pGGumS4p6bA+LedTUO+bjyjUP/dxykw=="}]},"_npmUser":{"name":"partouf-at-bindist","email":"dev@bindist.eu"},"directories":{},"maintainers":[{"name":"partouf-at-bindist","email":"dev@bindist.eu"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dynamo-to-pg_1.0.0_1777394203978_0.24740487203479944"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T16:36:43.852Z","1.0.0":"2026-04-28T16:36:44.106Z","modified":"2026-04-28T16:36:44.371Z"},"maintainers":[{"name":"partouf-at-bindist","email":"dev@bindist.eu"}],"description":"Translate DynamoDB operations to PostgreSQL queries","keywords":["dynamodb","postgresql","adapter"],"license":"MIT","readme":"# dynamo-to-pg\n\nDrop-in adapter that translates DynamoDB DocumentClient commands into PostgreSQL queries. Swap your data layer from DynamoDB to PostgreSQL without rewriting application code.\n\n```typescript\nimport pg from 'pg';\nimport { Dynamo2Pg } from 'dynamo-to-pg';\nimport { GetCommand } from '@aws-sdk/lib-dynamodb';\n\nconst pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });\nconst adapter = new Dynamo2Pg({\n  pool,\n  tables: [{ suffix: 'users', pk: 'userId' }],\n});\n\n// Your existing DynamoDB code works as-is — queries go to PostgreSQL now\nconst result = await adapter.send(\n  new GetCommand({ TableName: 'myapp-dev-users', Key: { userId: '123' } })\n);\nconsole.log(result.Item);\n```\n\n## Why\n\nMigrating from DynamoDB to PostgreSQL usually means rewriting every data access call. This library lets you keep your existing `@aws-sdk/lib-dynamodb` command objects (`GetCommand`, `PutCommand`, `QueryCommand`, etc.) and routes them to PostgreSQL instead. You can migrate incrementally, table by table, without a big-bang rewrite.\n\n## Install\n\n```bash\nnpm install dynamo-to-pg\n```\n\nRequires Node.js 20+ (ESM only). Peer dependency: `@aws-sdk/lib-dynamodb` (v3).\n\n## Full example\n\n```typescript\nimport pg from 'pg';\nimport { Dynamo2Pg } from 'dynamo-to-pg';\nimport { GetCommand, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';\n\nconst pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });\n\nconst adapter = new Dynamo2Pg({\n  pool,\n  tables: [\n    {\n      suffix: 'users',        // matches DynamoDB table \"myapp-dev-users\"\n      pk: 'userId',\n    },\n    {\n      suffix: 'orders',       // matches \"myapp-dev-orders\"\n      pk: 'userId',\n      sk: 'orderId',\n    },\n  ],\n});\n\n// Put an item\nawait adapter.send(\n  new PutCommand({\n    TableName: 'myapp-dev-users',\n    Item: { userId: '456', name: 'Alice', email: 'alice@example.com' },\n  })\n);\n\n// Get an item\nconst user = await adapter.send(\n  new GetCommand({ TableName: 'myapp-dev-users', Key: { userId: '456' } })\n);\nconsole.log(user.Item); // { userId: '456', name: 'Alice', email: 'alice@example.com' }\n\n// Query with a key condition\nconst orders = await adapter.send(\n  new QueryCommand({\n    TableName: 'myapp-dev-orders',\n    KeyConditionExpression: 'userId = :uid',\n    ExpressionAttributeValues: { ':uid': '123' },\n  })\n);\nconsole.log(orders.Items);\n```\n\n## How it works\n\n### Table name resolution\n\nDynamoDB table names like `myapp-dev-users` are split into a **schema prefix** (`myapp-dev`) and a **suffix** (`users`). The suffix is matched against your table config (longest match first). Hyphens in the suffix are converted to underscores for the SQL table name.\n\n| DynamoDB table name | Suffix config | SQL table |\n|---|---|---|\n| `myapp-dev-users` | `users` | `\"myapp-dev\".\"users\"` |\n| `myapp-dev-order-items` | `order-items` | `\"myapp-dev\".\"order_items\"` |\n| `users` | `users` (with `publicSchema: true`) | `\"users\"` |\n\nYou can override the derived SQL name with `sqlName`:\n\n```typescript\n{ suffix: 'order-items', sqlName: 'line_items', pk: 'orderId', sk: 'itemId' }\n```\n\n### Supported commands\n\n| DynamoDB command | PostgreSQL translation |\n|---|---|\n| `GetCommand` | `SELECT ... WHERE pk = $1 LIMIT 1` |\n| `PutCommand` | `INSERT ... ON CONFLICT DO UPDATE` (upsert) |\n| `QueryCommand` | `SELECT ... WHERE ... ORDER BY` with pagination |\n| `ScanCommand` | `SELECT ... ` with optional filter and pagination |\n| `UpdateCommand` | `UPDATE ... SET ... WHERE ... RETURNING *` |\n| `DeleteCommand` | `DELETE FROM ... WHERE ...` |\n| `BatchWriteCommand` | Multiple `INSERT` / `DELETE` statements, wrapped in a transaction when the pool supports `connect()` |\n\n### Supported expressions\n\n**Condition expressions** (`KeyConditionExpression`, `FilterExpression`):\n- Comparisons: `=`, `<>`, `<`, `>`, `<=`, `>=`\n- `BETWEEN ... AND ...`\n- `begins_with(attr, :val)` (translated to `LIKE`)\n- `attribute_exists(attr)` / `attribute_not_exists(attr)` (translated to `IS NOT NULL` / `IS NULL`)\n- `AND`, `OR`, parentheses\n\n**Update expressions** (`UpdateExpression`):\n- `SET attr = :val`\n- `SET attr = attr + :val` (increment)\n- `SET attr = if_not_exists(attr, :default)` (coalesce)\n- `SET attr = if_not_exists(attr, :default) + :val`\n- `SET obj.nested = :val` (translated to `jsonb_set`)\n- `ADD attr :val` (translated to `COALESCE + addition`)\n- `REMOVE obj.key` (translated to jsonb key removal)\n\n**Other features**:\n- `ExpressionAttributeNames` (`#name` placeholders)\n- `ExclusiveStartKey` pagination\n- `ScanIndexForward` ordering\n- `Select: 'COUNT'`\n- GSI queries via `IndexName`\n\n### Current limitations\n\n- **`ConditionExpression`** — conditional writes on `PutCommand`, `UpdateCommand`, and `DeleteCommand` are silently ignored. All writes are unconditional.\n- **`ProjectionExpression`** — all queries return `SELECT *`. Projection expressions are accepted but have no effect.\n- **Scan pagination** — `ScanCommand` uses a synthetic `{ _offset: N }` as `ExclusiveStartKey` rather than real DynamoDB-style pagination tokens. Existing pagination code that passes through opaque DynamoDB tokens will need adjustment.\n\n## PostgreSQL schema setup\n\nCreate PostgreSQL tables that mirror your DynamoDB tables. Column names should match DynamoDB attribute names exactly. Example for the quick start above:\n\n```sql\nCREATE SCHEMA \"myapp-dev\";\n\nCREATE TABLE \"myapp-dev\".\"users\" (\n  \"userId\"  TEXT PRIMARY KEY,\n  \"name\"    TEXT,\n  \"email\"   TEXT\n);\n\nCREATE TABLE \"myapp-dev\".\"orders\" (\n  \"userId\"   TEXT NOT NULL,\n  \"orderId\"  TEXT NOT NULL,\n  \"amount\"   NUMERIC,\n  \"status\"   TEXT,\n  PRIMARY KEY (\"userId\", \"orderId\")\n);\n```\n\nTips:\n- Use `TEXT` for string attributes, `NUMERIC` for numbers, `JSONB` for maps/lists\n- The primary key must match your `pk` (and `sk` if defined) in the table config\n- Create indexes on GSI columns for query performance\n\n## Migration strategy\n\n1. **Create PostgreSQL tables** matching your DynamoDB schema\n2. **Migrate data** from DynamoDB to PostgreSQL (use a script, AWS DMS, or a custom ETL)\n3. **Replace your DynamoDB client** with `Dynamo2Pg` — your application code stays the same\n4. **Run both in parallel** (optional) to validate correctness before cutting over\n5. **Remove the adapter** over time and use PostgreSQL queries directly where beneficial\n\n## API\n\n### `new Dynamo2Pg({ pool, tables })`\n\n- **`pool`** — any object implementing `query(text, values)` and `end()`. Works with `pg.Pool`, `pg.Client`, or any compatible wrapper. If the pool also provides `connect()` (as `pg.Pool` does), `BatchWriteCommand` will run inside a transaction — all puts/deletes commit atomically, or roll back on error.\n- **`tables`** — array of table configurations:\n  - `suffix` — DynamoDB table name suffix used for matching\n  - `pk` — partition key column name\n  - `sk` — (optional) sort key column name\n  - `gsis` — (optional) GSI definitions: `{ [indexName]: { pk, sk? } }`\n  - `publicSchema` — (optional) if `true`, skips schema qualification\n  - `sqlName` — (optional) override the auto-derived SQL table name\n\n### `adapter.send(command)`\n\nAccepts any supported `@aws-sdk/lib-dynamodb` command and returns a promise with the same response shape as DynamoDB.\n\n### `adapter.resolveTable(dynamoName)`\n\nReturns `{ sqlName, meta }` for a given DynamoDB table name. Useful for debugging or building custom queries.\n\n### `adapter.configureTables(tables)` / `adapter.registerTable(suffix, sqlName, meta)`\n\nReconfigure or extend table mappings at runtime.\n\n## License\n\nISC\n","readmeFilename":"README.md","_rev":"1-c18ad87ab27f700d243f30a255253351"}