Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework of the Shape type system #102

Merged
merged 77 commits into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
e4200fc
Classes with shape properties supporting annotations
sam-goodwin Jan 2, 2020
5f69d72
Scaffolding for new shape system established
sam-goodwin Jan 3, 2020
2a1bcb7
Stash kinda workiung:
sam-goodwin Jan 3, 2020
4bd88a3
Type-safe JSON schema transformation of Shape AST
sam-goodwin Jan 3, 2020
ac6b9cb
Collection types
sam-goodwin Jan 3, 2020
5ae5b16
Timestamp type and nice refactors
sam-goodwin Jan 4, 2020
c5e51de
Comments
sam-goodwin Jan 4, 2020
1aac30b
Of and of utilities
sam-goodwin Jan 4, 2020
6865b2d
Centralize ShapeGuards
sam-goodwin Jan 4, 2020
ef1c7aa
Tests for ShapeGuards
sam-goodwin Jan 4, 2020
5f8bcc9
Support decorators
sam-goodwin Jan 4, 2020
c756b08
Stash
sam-goodwin Jan 4, 2020
7d3da58
Type-safe meta annotations
sam-goodwin Jan 4, 2020
786b97f
Type-safe annotation plumbing is wired up
sam-goodwin Jan 4, 2020
bc8fd6a
Maintain the root Shape type of applied metadata to cleanup signature…
sam-goodwin Jan 5, 2020
9aa1202
Support numeric constraints for json schema
sam-goodwin Jan 5, 2020
3d27a96
Shape runtime mappings
sam-goodwin Jan 5, 2020
e0f3013
Runtime type mappings
sam-goodwin Jan 5, 2020
dd08560
Make better use of Symbols to stash metadatra
sam-goodwin Jan 5, 2020
f2f01e0
Support declaration merging
sam-goodwin Jan 5, 2020
df6d5b0
Map Shape to DynamoDB AttributeValue AST
sam-goodwin Jan 5, 2020
8bdadc0
Attribute Value mapper is working and is type-safe
sam-goodwin Jan 5, 2020
dd1cf86
Framework in place for DDB query expressions
sam-goodwin Jan 5, 2020
4652e2e
Grooming
sam-goodwin Jan 5, 2020
5fc0ca1
Better AST for queries
sam-goodwin Jan 6, 2020
95ab4b1
Query compilation is working
sam-goodwin Jan 6, 2020
3766b2b
Rename query to dsl
sam-goodwin Jan 6, 2020
2f6527c
move member enumeration into the AttributeValue.Struct type itself
sam-goodwin Jan 6, 2020
f4409f5
Move type enumeration into the types for AttributeValue
sam-goodwin Jan 6, 2020
c139984
rename query to filter
sam-goodwin Jan 6, 2020
937f912
Support assign and increment/decrement update expressions
sam-goodwin Jan 6, 2020
5405430
List push and concat
sam-goodwin Jan 6, 2020
a3101a6
DynamoDB Table Client partial
sam-goodwin Jan 6, 2020
505aba5
Remove DDB visitor
sam-goodwin Jan 6, 2020
21a76b0
Support disabling ClassShape cache
sam-goodwin Jan 6, 2020
3e30ca1
fix ShapeGuards assert message
sam-goodwin Jan 6, 2020
63fbcfa
Remove ClassModel type alias and clean up Member derivations
sam-goodwin Jan 6, 2020
3a21a20
Grooming
sam-goodwin Jan 6, 2020
75f947c
DDB client unit tests
sam-goodwin Jan 6, 2020
a8fe5a5
hashCode, equals, data structures, json de/ser
sam-goodwin Jan 7, 2020
244ca08
Required and optional kets. Fixes to bugs for runtime types and json …
sam-goodwin Jan 8, 2020
ddc6179
Fix broken types introduced by Member.Map :(.
sam-goodwin Jan 8, 2020
073038c
Support array and map indexing
sam-goodwin Jan 8, 2020
fb7b262
JSON Path AST
sam-goodwin Jan 8, 2020
df17080
Dynamic and Binary Shapes
sam-goodwin Jan 8, 2020
f37275c
Groom
sam-goodwin Jan 8, 2020
2c7c463
Validator logic
sam-goodwin Jan 8, 2020
06243c1
Validate DynamoDB records
sam-goodwin Jan 8, 2020
f08fc85
Add path information to validation
sam-goodwin Jan 9, 2020
e9851be
add shapes-glue mappings
sam-goodwin Jan 10, 2020
e193945
Big stuff lol
sam-goodwin Jan 15, 2020
edd2a4c
Implement Record function to create classes dynamically
sam-goodwin Jan 24, 2020
bd7ad71
Fixing a bunch of packages
sam-goodwin Jan 28, 2020
3486324
Fix JSON Path
sam-goodwin Jan 28, 2020
dd2eb9f
punchcard is compiling
sam-goodwin Jan 28, 2020
c701f8d
Most tests are passing
sam-goodwin Jan 29, 2020
fa3fc08
Clean up how Data Type was configured for a data structure
sam-goodwin Jan 30, 2020
4a0efdf
Data lake compiles.
sam-goodwin Jan 30, 2020
c9341ab
Tests are mostly working
sam-goodwin Jan 30, 2020
4d639ce
Rename ClassShape to RecordShape
sam-goodwin Feb 4, 2020
55eaf19
Add README to the shape lib
sam-goodwin Feb 4, 2020
93531f8
More READMEs
sam-goodwin Feb 4, 2020
cadb188
Rename shape-glue to shape-hive
sam-goodwin Feb 4, 2020
9500be7
Update snapshot tests and fix empty ({}) Record bugs
sam-goodwin Feb 4, 2020
3f39c7e
Upgrade to cdk 1.22.0
sam-goodwin Feb 4, 2020
105d9a8
Fix bugs with DDB update expressions
sam-goodwin Feb 4, 2020
5437290
Fix bootstrap bugs - got hello-world example working
sam-goodwin Feb 4, 2020
7c7d977
Stream-processing example is working
sam-goodwin Feb 4, 2020
40055f9
Updates and cleanups for README.md. Support condition expressions for…
sam-goodwin Feb 4, 2020
f29d168
Fix snapshot tests
sam-goodwin Feb 4, 2020
503c229
Update docs
sam-goodwin Feb 4, 2020
ab775d5
Move all @punchcard/shape-json exports into the Json namespace
sam-goodwin Feb 4, 2020
764a1ac
Fix Glue typos on README
sam-goodwin Feb 4, 2020
d758678
Fix Glue typos on README
sam-goodwin Feb 4, 2020
e864e4f
Update package json
sam-goodwin Feb 4, 2020
6cbd347
Merge branch 'master' into struct-overhaul
sam-goodwin Feb 4, 2020
d130e1d
Fix package locks
sam-goodwin Feb 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions examples/lib/data-lake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@ import { Core, Lambda } from 'punchcard';

import * as Analytics from '@punchcard/data-lake';

import { integer, string, timestamp, char, array, } from 'punchcard/lib/shape';
import { Record, string, array, integer, timestamp, Shape } from '@punchcard/shape';
import { char } from '@punchcard/shape-glue';

export const app = new Core.App();
const stack = app.root.map(app => new core.Stack(app, 'data-lake'));

class DataPoint extends Record({
key: string,
value: char(10),
data_points: array(integer),
timestamp
}) {}

// create a schema to describe our data
const dataPoints = new Analytics.Schema({
schemaName: 'data_points',
shape: {
key: string(),
value: char(10),
data_points: array(integer()),
timestamp
},
shape: DataPoint,
timestampField: 'timestamp'
});

Expand Down Expand Up @@ -53,12 +56,10 @@ Lambda.schedule(stack, 'DummyDataPoints', {
depends: lake.pipelines.dataPoints.stream.writeAccess(),
schedule: Schedule.rate(Duration.minutes(1)),
}, async (_, stream) => {
await stream.putRecord({
Data: {
key: 'key',
data_points: [0, 1, 2],
timestamp: new Date(),
value: 'some-value'
}
});
await stream.putRecord(new DataPoint({
key: 'key',
data_points: [0, 1, 2],
timestamp: new Date(),
value: 'some-value'
}));
});
169 changes: 74 additions & 95 deletions examples/lib/dynamodb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,63 @@ import { Schedule } from '@aws-cdk/aws-events';

import { Core, DynamoDB, Lambda } from 'punchcard';

import { array, dynamic, string, integer, struct } from 'punchcard/lib/shape';
import { array, string, integer, Record, any, Shape, Minimum } from '@punchcard/shape';
import { Build } from 'punchcard/lib/core/build';

export const app = new Core.App();

const stack = app.root.map(app => new cdk.Stack(app, 'invoke-function'));

type Item = typeof Item;
const Item = {
id: string(),
count: integer({
minimum: 0
}),
name: string(),
array: array(string()),
struct: struct({
key: string(),
number: integer()
}),
any: dynamic
}
class Struct extends Record({
key: string,
number: integer
}) {}

class Item extends Record({
id: string,
count: integer
.apply(Minimum(0)),
name: string,
array: array(string),
struct: Struct,
any
}) {}

// the type can be inferred, but we explicitly define them to illustrate how it works
// 'id' is the partitionKey, undefined is the sortKey (no sort key), and Item is the attributes of data in the table
const table: DynamoDB.Table<'id', undefined, Item> = new DynamoDB.Table(stack, 'hash-table', {
partitionKey: 'id',
attributes: Item,
tableProps: Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
})
});
const table = new DynamoDB.Table(stack, 'hash-table', Item, 'id', Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
}));

// 'count' is the sortKey in this case
const sortedTable: DynamoDB.Table<'id', 'count', Item> = new DynamoDB.Table(stack, 'sorted-table', {
partitionKey: 'id',
sortKey: 'count',
attributes: Item,
tableProps: Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
})
});
const sortedTable = new DynamoDB.Table(stack, 'sorted-table', Item, ['id', 'count'], Build.of({
billingMode: BillingMode.PAY_PER_REQUEST
}));

// call the incrementer function from another Lambda Function
Lambda.schedule(stack, 'Caller', {
depends: Core.Dependency.concat(table.readWriteAccess(), sortedTable.readAccess()),
schedule: Schedule.rate(Duration.minutes(1)),
}, async (_, [table, sortedTable]) => {
await table.get({
id: 'id',
});
await table.get('id');

await table.put({
await table.putIf(new Item({
// the item is type-safe and well structured
item: {
id: 'id',
count: 1,
name: 'name',
any: {
a: 'value'
},
array: ['some', 'values'],
struct: {
key: 'value',
number: 1
}
id: 'id',
count: 1,
name: 'name',
any: {
a: 'value'
},
array: ['some', 'values'],
struct: new Struct({
key: 'value',
number: 1
})
}), item =>
// condition expressions are generated with a nice type-safe DSL
if: item => item.count.eq(0)
.or(DynamoDB.not(item.count.greaterThan(0)))
item.count.equals(0)
// .or(DynamoDB.not(item.count.greaterThan(0)))
.and(
// string
item.name.equals('name'),
Expand All @@ -82,67 +70,58 @@ Lambda.schedule(stack, 'Caller', {
// arrays
item.array.length.greaterThan(0),
item.array.equals(['a', 'b']),
item.array.at(0).equals('a'))
item.array[0].equals('a'))
.and(
// structs
item.struct.eq({
item.struct.equals(new Struct({
key: 'value',
number: 1
}),
})),
item.struct.fields.key.equals('value'),
item.struct.fields.key.length.greaterThanOrEqual(5),
item.struct.fields.number.lessThan(0))
.and(
// the dynamic type is casted to a static type before building a condition expression
item.any
.as(string())
.lessThan('value'),
item.any.as(integer()).greaterThanOrEqual(1),
item.any.as(array(integer())).equals([1]),
item.any.as(array(integer())).at(0).lessThanOrEqual(1),
item.any.as(struct({
key: string()
})).eq({
key: 'value'
}))
});
.as(string)
.equals('value'),
item.any.as(integer).greaterThanOrEqual(1),
item.any.as(array(integer)).equals([1]),
item.any.as(array(integer))[0].lessThanOrEqual(1),
item.any.as(Shape.of(Struct)).equals(new Struct({
key: 'value',
number: 1
})))
);

await table.update({
key: {
id: 'id',
},
actions: item => [
// strings
item.name.set('name'), // item.name = 'name'
// numbers
item.count.set(1), // item.count = 1
item.count.decrement(1), // item.count -- or item.count -= 1
item.count.increment(1), // item.count += 1
item.count.set(item.count.plus(1)), // explicitly: item.count += 1

// structs
item.count.set(item.struct.fields.number), // item.count = item.struct.number
item.struct.set({ // item.struct = {
key: 'value', // key: 'value',
number: 1 // number: 1
}), // }
item.struct.fields.key.set('value'), // item.struct.key = 'value'
await table.update('id', item => [
// strings
item.name.set('name'), // item.name = 'name'
// numbers
item.count.set(1), // item.count = 1
item.count.decrement(1), // item.count -- or item.count -= 1
item.count.increment(1), // item.count += 1
item.count.set(item.count.plus(1)), // explicitly: item.count += 1

// structs
item.count.set(item.struct.fields.number), // item.count = item.struct.number
item.struct.set(new Struct({ // item.struct = {
key: 'value', // key: 'value',
number: 1 // number: 1
})), // }
item.struct.fields.key.set('value'), // item.struct.key = 'value'

// arrays
item.array.set(['some', 'values']), // item.array = ['some', 'values']
item.name.set(item.array.at(0)), // item.name = item.array[0]
item.array.at(1).set('value'), // item.array[0] = 'value'
item.array.at(1).set(item.name), // item.array[0] = item.name
],
if: item => DynamoDB.attribute_exists(item.id),
});
// arrays
item.array.set(['some', 'values']), // item.array = ['some', 'values']
item.name.set(item.array[0]), // item.name = item.array[0]
item.array[1].set('value'), // item.array[0] = 'value'
item.array[1].set(item.name), // item.array[0] = item.name
], //item => DynamoDB.attribute_exists(item.id),
);

// sorted tables can be queried
await sortedTable.query({
key: {
id: 'id',
count: DynamoDB.greaterThan(1)
},
await sortedTable.query(['id', count => count.greaterThan(1)], {

filter: item => item.array.length.equals(item.count) // item.array.lenth === item.count
});
});
58 changes: 55 additions & 3 deletions examples/lib/hello-world.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,62 @@
import cdk = require('@aws-cdk/core');
import { Schedule } from '@aws-cdk/aws-events';
import { Core, Lambda } from 'punchcard';
import { Core, Lambda, DynamoDB, SQS } from 'punchcard';
import { string, integer, Record, MaxLength } from '@punchcard/shape';
import { Dependency } from 'punchcard/lib/core';

import json = require('@punchcard/shape-json');

export const app = new Core.App();
const stack = app.root.map(app => new cdk.Stack(app, 'hello-world'));

/**
* State of a counter.
*/
class Counter extends Record({

/**
* Rate Key documentation goes here.
*/
key: string
.apply(MaxLength(1)),

count: integer
}) {}

const hashTable = new DynamoDB.Table(stack, 'Table', Counter, 'key');

const queue = new SQS.Queue(stack, 'queue', {
shape: Counter
});

// schedule a Lambda function to increment counts in DynamoDB and send SQS messages with each update.
Lambda.schedule(stack, 'MyFunction', {
schedule: Schedule.rate(cdk.Duration.minutes(1))
}, async() => console.log('Hello, World!'));
schedule: Schedule.rate(cdk.Duration.minutes(1)),
depends: Dependency.concat(
hashTable.readWriteAccess(),
queue.sendAccess()),
}, async(_, [hashTable, queue]) => {
console.log('Hello, World!');

let rateType = await hashTable.get('hash key');
if (rateType === undefined) {
rateType = new Counter({
key: 'key',
count: 0
})
await hashTable.put(rateType);
}

await queue.sendMessage(rateType);
await hashTable.update('key', _ => [
_.count.increment()
]);
});

// print out a message for each SQS message received
queue.messages().forEach(stack, 'ForEachMessage', {}, async (msg) => {
console.log(`received message with key ${msg.key} and count ${msg.count}`);
});



Loading