Last active
January 3, 2026 19:36
-
-
Save tywalch/321e033a97026b4893bcff7e8117eebb to your computer and use it in GitHub Desktop.
ElectroDB Issue 540
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // THIS FILE USES THE SAME MODELS SPECIFIED IN THE PROVIDED PLAYGROUND | |
| import { Entity as Entity0, Service as Service0 } from "threedotfivedotone"; | |
| import { Entity as Entity1, Service as Service1 } from "threedotfivedotzero"; | |
| import { Entity as Entity2, Service as Service2 } from "threedotfivedottwo"; | |
| import { DynamoDB } from "aws-sdk"; | |
| import { expect } from "chai"; | |
| const dynamodb = new DynamoDB({ | |
| region: "us-east-1", | |
| endpoint: "http://localhost:8000", | |
| credentials: { | |
| accessKeyId: "test", | |
| secretAccessKey: "test", | |
| }, | |
| }); | |
| const manager = { | |
| async exists(tableName: string) { | |
| let tables = await dynamodb.listTables().promise(); | |
| return !!(tables.TableNames || []).includes(tableName); | |
| }, | |
| async drop(tableName: string) { | |
| return dynamodb.deleteTable({ TableName: tableName }).promise(); | |
| }, | |
| async create(tableName: string, tableDefinition: any) { | |
| return dynamodb | |
| .createTable({ ...tableDefinition, TableName: tableName }) | |
| .promise(); | |
| }, | |
| }; | |
| const client = new DynamoDB.DocumentClient({ | |
| region: "us-east-1", | |
| endpoint: "http://localhost:8000", | |
| credentials: { | |
| accessKeyId: "test", | |
| secretAccessKey: "test", | |
| }, | |
| }); | |
| function createZeroService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity0( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.0', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity0( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.0', | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service0({ tenancy, apiKey }); | |
| } | |
| function createOneService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity1( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.1', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity1( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.1', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service1({ tenancy, apiKey }); | |
| } | |
| function createTwoService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity2( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.2', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity2( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.2', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: [] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service2({ tenancy, apiKey }); | |
| } | |
| const createTable = async (table: string) => { | |
| if (await manager.exists(table)) { | |
| console.log('table already exists, dropping...'); | |
| await manager.drop(table); | |
| console.log('table dropped'); | |
| } | |
| console.log('creating table...'); | |
| await manager.create(table, { | |
| "KeySchema": [ | |
| { | |
| "AttributeName": "pk", | |
| "KeyType": "HASH" | |
| }, | |
| { | |
| "AttributeName": "sk", | |
| "KeyType": "RANGE" | |
| } | |
| ], | |
| "AttributeDefinitions": [ | |
| { | |
| "AttributeName": "pk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "sk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "gsi1pk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "gsi1sk", | |
| "AttributeType": "S" | |
| } | |
| ], | |
| "GlobalSecondaryIndexes": [ | |
| { | |
| "IndexName": "gsi1pk-gsi1sk-index", | |
| "KeySchema": [ | |
| { | |
| "AttributeName": "gsi1pk", | |
| "KeyType": "HASH" | |
| }, | |
| { | |
| "AttributeName": "gsi1sk", | |
| "KeyType": "RANGE" | |
| } | |
| ], | |
| "Projection": { | |
| "ProjectionType": "ALL" | |
| } | |
| }, | |
| ], | |
| "BillingMode": "PAY_PER_REQUEST" | |
| }); | |
| console.log('table created'); | |
| } | |
| type ZeroService = ReturnType<typeof createZeroService>; | |
| type OneService = ReturnType<typeof createOneService>; | |
| type TwoService = ReturnType<typeof createTwoService>; | |
| type TestService = ZeroService | OneService | TwoService; | |
| async function run(mainService: TestService, otherServices: TestService[]) { | |
| const tenancyId = 'myTenancyId'; | |
| const apiKeyId = 'myApiKeyId'; | |
| await mainService.entities.apiKey.put({ tenancyId, apiKeyId, token: 'myApiKey' }).go(); | |
| await mainService.entities.tenancy.put({ tenancyId, name: 'my tenancy' }).go(); | |
| const params = [ | |
| mainService.collections.settings({ tenancyId }).params(), | |
| ...otherServices.map(service => service.collections.settings({ tenancyId }).params()) | |
| ] | |
| const query = await Promise.all([ | |
| mainService.collections.settings({ tenancyId }).go(), | |
| ...otherServices.map(service => service.collections.settings({ tenancyId }).go()) | |
| ]); | |
| return { | |
| params, | |
| query, | |
| }; | |
| } | |
| function createServices(tablename: string) { | |
| return { | |
| zeroService: createZeroService(tablename), | |
| oneService: createOneService(tablename), | |
| twoService: createTwoService(tablename) | |
| } | |
| } | |
| async function main() { | |
| const table = "issuefivefourzero"; | |
| const services = createServices(table); | |
| const testCases = [ | |
| { | |
| description: 'create items with 3.5.0, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.zeroService, | |
| otherServices: [services.oneService, services.twoService], | |
| }, | |
| { | |
| description: 'create items with 3.5.1, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.oneService, | |
| otherServices: [services.zeroService, services.twoService], | |
| }, | |
| { | |
| description: 'create items with 3.5.2, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.twoService, | |
| otherServices: [services.zeroService, services.oneService], | |
| }, | |
| ] | |
| for (const testCase of testCases) { | |
| await createTable(table); | |
| console.log(`Running test case: ${testCase.description}`); | |
| const results = await run(testCase.mainService, testCase.otherServices); | |
| expect(results.params.length).to.equal(3); | |
| expect(results.query.length).to.equal(3); | |
| for (let i = 0; i < 3; i++) { | |
| const leftParam = results.params[i]; | |
| const leftQuery = results.query[i]; | |
| for (let j = i + 1; j < 3; j++) { | |
| const rightParam = results.params[j]; | |
| const rightQuery = results.query[j]; | |
| expect(leftParam).to.deep.equal(rightParam); | |
| expect(leftQuery).to.deep.equal(rightQuery); | |
| } | |
| } | |
| console.log('test case passed'); | |
| } | |
| } | |
| main().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // THIS FILE USES THE A VARITION MODELS SPECIFIED IN THE PROVIDED PLAYGROUND: UNQUIE SORT KEY COMPOSITE ATTRIBUTES BETWEEN THE TENANCY AND APIKEY ENTITIES | |
| import { Entity as Entity0, Service as Service0 } from "threedotfivedotone"; | |
| import { Entity as Entity1, Service as Service1 } from "threedotfivedotzero"; | |
| import { Entity as Entity2, Service as Service2 } from "threedotfivedottwo"; | |
| import { DynamoDB } from "aws-sdk"; | |
| import { expect } from "chai"; | |
| const dynamodb = new DynamoDB({ | |
| region: "us-east-1", | |
| endpoint: "http://localhost:8000", | |
| credentials: { | |
| accessKeyId: "test", | |
| secretAccessKey: "test", | |
| }, | |
| }); | |
| const manager = { | |
| async exists(tableName: string) { | |
| let tables = await dynamodb.listTables().promise(); | |
| return !!(tables.TableNames || []).includes(tableName); | |
| }, | |
| async drop(tableName: string) { | |
| return dynamodb.deleteTable({ TableName: tableName }).promise(); | |
| }, | |
| async create(tableName: string, tableDefinition: any) { | |
| return dynamodb | |
| .createTable({ ...tableDefinition, TableName: tableName }) | |
| .promise(); | |
| }, | |
| }; | |
| const client = new DynamoDB.DocumentClient({ | |
| region: "us-east-1", | |
| endpoint: "http://localhost:8000", | |
| credentials: { | |
| accessKeyId: "test", | |
| secretAccessKey: "test", | |
| }, | |
| }); | |
| function createZeroService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity0( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| tenancyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.0', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: ["tenancyField"] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity0( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.0', | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: ["apiKeyField"] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service0({ tenancy, apiKey }); | |
| } | |
| function createOneService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity1( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.1', | |
| }, | |
| tenancyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: ["tenancyField"] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity1( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.1', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: ["apiKeyField"] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service1({ tenancy, apiKey }); | |
| } | |
| function createTwoService(tableName: string) { | |
| /* Tasks Entity */ | |
| const tenancy = new Entity2( | |
| { | |
| model: { | |
| entity: "tenancy", | |
| version: "1", | |
| service: "tenancy" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| name: { | |
| type: "string", | |
| required: true | |
| }, | |
| tenancyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.2', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| // cannot be modified after created | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| // watch for changes to any attribute | |
| watch: "*", | |
| // set current timestamp when updated | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| field: "pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| field: "sk", | |
| // create composite keys for partial sort key queries | |
| composite: [] | |
| } | |
| }, | |
| settings: { | |
| // collections allow for queries across multiple entities | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| // map to your GSI Hash/Partition key | |
| field: "gsi1pk", | |
| composite: ["tenancyId"] | |
| }, | |
| sk: { | |
| // map to your GSI Range/Sort key | |
| field: "gsi1sk", | |
| composite: ["tenancyField"] | |
| } | |
| }, | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| /* Users Entity */ | |
| const apiKey = new Entity2( | |
| { | |
| model: { | |
| entity: "apiKey", | |
| service: "tenancy", | |
| version: "1" | |
| }, | |
| attributes: { | |
| tenancyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyId: { | |
| type: "string", | |
| required: true | |
| }, | |
| token: { | |
| type: "string", | |
| required: true | |
| }, | |
| apiKeyField: { | |
| type: "string", | |
| required: true | |
| }, | |
| version: { | |
| type: "string", | |
| default: () => '3.5.2', | |
| }, | |
| createdAt: { | |
| type: "number", | |
| default: () => Date.now(), | |
| readOnly: true | |
| }, | |
| updatedAt: { | |
| type: "number", | |
| watch: "*", | |
| set: () => Date.now(), | |
| readOnly: true | |
| } | |
| }, | |
| indexes: { | |
| primary: { | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "pk" | |
| }, | |
| sk: { | |
| composite: ["token"], | |
| field: "sk" | |
| } | |
| }, | |
| settings: { | |
| collection: "settings", | |
| index: "gsi1pk-gsi1sk-index", | |
| pk: { | |
| composite: ["tenancyId"], | |
| field: "gsi1pk" | |
| }, | |
| sk: { | |
| field: "gsi1sk", | |
| composite: ["apiKeyField"] | |
| } | |
| } | |
| } | |
| }, | |
| { table: tableName, client } | |
| ); | |
| return new Service2({ tenancy, apiKey }); | |
| } | |
| const createTable = async (table: string) => { | |
| if (await manager.exists(table)) { | |
| console.log('table already exists, dropping...'); | |
| await manager.drop(table); | |
| console.log('table dropped'); | |
| } | |
| console.log('creating table...'); | |
| await manager.create(table, { | |
| "KeySchema": [ | |
| { | |
| "AttributeName": "pk", | |
| "KeyType": "HASH" | |
| }, | |
| { | |
| "AttributeName": "sk", | |
| "KeyType": "RANGE" | |
| } | |
| ], | |
| "AttributeDefinitions": [ | |
| { | |
| "AttributeName": "pk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "sk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "gsi1pk", | |
| "AttributeType": "S" | |
| }, | |
| { | |
| "AttributeName": "gsi1sk", | |
| "AttributeType": "S" | |
| } | |
| ], | |
| "GlobalSecondaryIndexes": [ | |
| { | |
| "IndexName": "gsi1pk-gsi1sk-index", | |
| "KeySchema": [ | |
| { | |
| "AttributeName": "gsi1pk", | |
| "KeyType": "HASH" | |
| }, | |
| { | |
| "AttributeName": "gsi1sk", | |
| "KeyType": "RANGE" | |
| } | |
| ], | |
| "Projection": { | |
| "ProjectionType": "ALL" | |
| } | |
| }, | |
| ], | |
| "BillingMode": "PAY_PER_REQUEST" | |
| }); | |
| console.log('table created'); | |
| } | |
| type ZeroService = ReturnType<typeof createZeroService>; | |
| type OneService = ReturnType<typeof createOneService>; | |
| type TwoService = ReturnType<typeof createTwoService>; | |
| type TestService = ZeroService | OneService | TwoService; | |
| async function run(mainService: TestService, otherServices: TestService[]) { | |
| const tenancyId = 'myTenancyId'; | |
| const apiKeyId = 'myApiKeyId'; | |
| await mainService.entities.apiKey.put({ tenancyId, apiKeyId, token: 'myApiKey', apiKeyField: 'myApiKeyField' }).go(); | |
| await mainService.entities.tenancy.put({ tenancyId, name: 'my tenancy', tenancyField: 'myTenancyField' }).go(); | |
| const params = [ | |
| mainService.collections.settings({ tenancyId }).params(), | |
| ...otherServices.map(service => service.collections.settings({ tenancyId }).params()) | |
| ] | |
| const query = await Promise.all([ | |
| mainService.collections.settings({ tenancyId }).go(), | |
| ...otherServices.map(service => service.collections.settings({ tenancyId }).go()) | |
| ]); | |
| return { | |
| params, | |
| query, | |
| }; | |
| } | |
| function createServices(tablename: string) { | |
| return { | |
| zeroService: createZeroService(tablename), | |
| oneService: createOneService(tablename), | |
| twoService: createTwoService(tablename) | |
| } | |
| } | |
| async function main() { | |
| const table = "issuefivefourzero"; | |
| const services = createServices(table); | |
| const testCases = [ | |
| { | |
| description: 'create items with 3.5.0, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.zeroService, | |
| otherServices: [services.oneService, services.twoService], | |
| }, | |
| { | |
| description: 'create items with 3.5.1, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.oneService, | |
| otherServices: [services.zeroService, services.twoService], | |
| }, | |
| { | |
| description: 'create items with 3.5.2, query with 3.5.0, 3.5.1, 3.5.2', | |
| mainService: services.twoService, | |
| otherServices: [services.zeroService, services.oneService], | |
| }, | |
| ] | |
| for (const testCase of testCases) { | |
| await createTable(table); | |
| console.log(`Running test case: ${testCase.description}`); | |
| const results = await run(testCase.mainService, testCase.otherServices); | |
| expect(results.params.length).to.equal(3); | |
| expect(results.query.length).to.equal(3); | |
| for (let i = 0; i < 3; i++) { | |
| const leftParam = results.params[i]; | |
| const leftQuery = results.query[i]; | |
| for (let j = i + 1; j < 3; j++) { | |
| const rightParam = results.params[j]; | |
| const rightQuery = results.query[j]; | |
| expect(leftParam).to.deep.equal(rightParam); | |
| expect(leftQuery).to.deep.equal(rightQuery); | |
| } | |
| } | |
| console.log('test case passed'); | |
| } | |
| } | |
| main().catch(console.error); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "issue-540", | |
| "version": "1.0.0", | |
| "description": "", | |
| "main": "index.js", | |
| "scripts": { | |
| "test": "echo \"Error: no test specified\" && exit 1", | |
| "start": "ts-node ./src/index.ts" | |
| }, | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "type": "commonjs", | |
| "dependencies": { | |
| "@types/aws-sdk": "^0.0.42", | |
| "aws-sdk": "^2.1693.0", | |
| "threedotfivedotone": "npm:electrodb@^3.5.1", | |
| "threedotfivedottwo": "npm:electrodb@^3.5.2", | |
| "threedotfivedotzero": "npm:electrodb@^3.5.0", | |
| "ts-node": "^10.9.2", | |
| "typescript": "^5.9.3" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment