Skip to content

Commit d41ba69

Browse files
author
Joshua Kelly
committedJan 6, 2020
Initial commit
0 parents  commit d41ba69

26 files changed

+9309
-0
lines changed
 

‎.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
ops/
3+
4+
src/server/.env
5+
src/server/dist

‎docker/docker-compose.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
version: '3.1'
2+
services:
3+
db:
4+
container_name: scaledger-db
5+
6+
image: postgres:11.6-alpine
7+
ports:
8+
- '5432:5432'
9+
volumes:
10+
- ../src/db:/tasks
11+
- ../src/db/schema/schema.sql:/docker-entrypoint-initdb.d/schema.sql
12+
environment:
13+
POSTGRES_USER: scaledger
14+
POSTGRES_PASSWORD: changeme
15+
POSTGRES_DB: scaledger
16+
server:
17+
container_name: scaledger-server
18+
build:
19+
context: ../src/server
20+
image: scaledger-server:latest
21+
ports:
22+
- 5000:5000
23+
links:
24+
- db
25+
environment:
26+
DATABASE_URL: postgres://scaledger:changeme@db/scaledger
27+
PORT: 5000
28+
AUTH_DATABASE_URL: postgres://scaledger:changeme@db/scaledger
29+
NODE_ENV: development
30+
HOST: http://localhost:5000
31+
depends_on:
32+
- db

‎docker/scripts/psql

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker exec -it -w /tasks scaledger-db psql -U scaledger scaledger

‎docker/scripts/schema

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
docker exec -e PGOPTIONS=--search_path=public -w /tasks/scripts scaledger-db ./schema
4+
echo 'schema rebuilt'

‎docker/scripts/seed

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker exec -w /tasks/scripts scaledger-db ./seed

‎docker/scripts/watch_schema

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
command -v inotifywait
4+
5+
./seed
6+
7+
inotifywait -q -m -e close_write \
8+
--format %e \
9+
../db/schema/schema.sql |
10+
11+
while read events; do
12+
./seed
13+
done

‎docker/up

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
docker-compose up

‎readme.md

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Scaledger
2+
3+
[![pipeline status](https://gitlab.com/df8org/scaledger/badges/master/pipeline.svg)](https://gitlab.com/df8org/scaledger/commits/master)
4+
5+
A double-entry accounting database with a typed GraphQL API, supporting:
6+
7+
- Immutable entries
8+
- An API introspected directly from a PostgreSQL schema
9+
- GraphQL subscriptions for real-time updates
10+
11+
## Basics
12+
13+
Scaledger is designed to be used as a service for recording transactions (called `postings`) between `accounts`, and reporting their balances through an entity called `ledger`. This is particularly useful when you have to track the balances for thousands, or even millions, of individual user accounts such as in the case of marketplaces.
14+
15+
To use it, you deploy it as part of your service architecture and connect over a GraphQL interface. Documentation this interface is available at [http://localhost:5000/graphiql](http://localhost:5000/graphiql)
16+
17+
First, create some `account`s:
18+
19+
```
20+
mutation {
21+
createAccount(input: {account: {type: EQUITY, name: "Y Combinator Seed"}}) {
22+
account {
23+
id
24+
}
25+
}
26+
}
27+
28+
mutation {
29+
createAccount(input: {account: {type: ASSET, name: "Cash"}}) {
30+
account {
31+
id
32+
}
33+
}
34+
}
35+
```
36+
37+
Next, create a `posting` between them:
38+
39+
```
40+
mutation {
41+
createPosting(input: { posting: {
42+
creditId: "5c70baa8-f917-4220-afad-1521fdecb5a7",
43+
debitId: "9c42c59a-7404-47f2-9a63-fb3a8ecab111",
44+
currency: USD,
45+
amount: 15000000,
46+
externalId: "yc-safe-transfer"
47+
}})
48+
}
49+
```
50+
51+
You'll notice that the `amount` field is denominated in the minor value of the currency. This is important - don't use floats for accounting systems! Next, you'll notice `currency` - Scaledger is natively multi-currency and supports all of the ISO 4217 currency codes. If you're wondering what `externalId` is, that's required so that each `posting` from a downstream service is idempotent - as a defense against you sending the same request twice.
52+
53+
Both `account` and `posting` also support a `metadata` field which can be used to store abitrary key/value JSON dictionaries of extra data. Like any good ledger, `posting` cannot be mutated after it is created - to void a transaction you need to reverse it by creating an inverted one.
54+
55+
The general ledger can be queried after `posting`s are created:
56+
57+
```
58+
query {
59+
ledgers {
60+
nodes {
61+
accountName
62+
balance
63+
currency
64+
}
65+
}
66+
}
67+
```
68+
69+
Lastly, Scaledger also supports WebSockets for newly created postings via the GraphQL Subscription primitive:
70+
71+
```
72+
subscription {
73+
postingCreated {
74+
posting {
75+
amount
76+
id
77+
}
78+
}
79+
}
80+
```
81+
82+
## Stack
83+
84+
Scaledger uses a purpose-built PostgreSQL schema and provides a GraphQL API.
85+
86+
### Services
87+
- *scaledger-db*: a PostgreSQL database and ledger schema
88+
- *scaledger-server*: a node-based [PostGraphile](https://www.graphile.org/) GraphQL API
89+
90+
## Development
91+
92+
scaledger includes a `docker-compose` configuration out of box.
93+
94+
1. Clone the project
95+
2. [Install Docker Compose](https://docs.docker.com/compose/install/)
96+
3. `cd docker`
97+
4. Build images with `./scripts/images`
98+
5. `docker-compose up`
99+
100+
By default, your first initialization of the container will automatically run the schema. Depending on how you've installed docker, you may need to prefix `sudo` on `docker-compose` up and on any of the scripts in `docker/scripts`.
101+
102+
If you'd like to create test data, run `./scripts/seed` from inside of `docker`. You can run `./seed` at any point to return the test data and schema to an initial state.
103+
104+
If you wish to recreate the database, or reset it, without any seed data you can run `./schema` instead.
105+
106+
To connect to the docker container running the database with `psql` run `./scripts/psql`.
107+
108+
### Services In Development
109+
- View `scaledger-server`'s GraphiQL testbed at [http://localhost:5000/graphiql](http://localhost:5000/graphiql)
110+
- `scaledger-server` is mounted at [http://localhost:5000/](http://localhost:5000)
111+
- `scaledger-db` runs on `5432`, use `./scripts/psql` to run a console

‎src/db/schema/schema.sql

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
begin;
2+
3+
-- Schemas
4+
create schema if not exists public;
5+
create schema if not exists scaledger_public;
6+
create schema if not exists scaledger_private;
7+
8+
9+
-- Extensions
10+
create extension if not exists "uuid-ossp";
11+
create extension if not exists "pgcrypto";
12+
13+
-- Types
14+
create type scaledger_public.account_type as enum (
15+
'asset',
16+
'equity',
17+
'expense',
18+
'liability',
19+
'revenue',
20+
'contra'
21+
);
22+
23+
comment on type scaledger_public.account_type is 'The possible types of accounts';
24+
25+
26+
create type scaledger_public.currency as enum (
27+
'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF',
28+
'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BTN', 'BWP', 'BYR', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY', 'COP', 'CRC',
29+
'CUC', 'CUP', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL',
30+
'GGP', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'IMP', 'INR',
31+
'IQD', 'IRR', 'ISK', 'JEP', 'JMD', 'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD', 'KZT',
32+
'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LYD', 'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR',
33+
'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'OMR', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR',
34+
'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS',
35+
'SPL', 'SRD', 'STD', 'SVC', 'SYP', 'SZL', 'THB', 'TJS', 'TMT', 'TND', 'TOP', 'TRY', 'TTD', 'TVD', 'TWD', 'TZS',
36+
'UAH', 'UGX', 'USD', 'UYU', 'UZS', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XCD', 'XDR', 'XOF', 'XPF', 'YER', 'ZAR',
37+
'ZMW', 'ZWD'
38+
);
39+
40+
41+
-- Global table Functions
42+
create function scaledger_private.set_updated_at() returns trigger as $$
43+
begin
44+
new.updated_at := current_timestamp;
45+
return new;
46+
end;
47+
$$ language plpgsql;
48+
49+
50+
create function scaledger_public.graphql_subscription() returns trigger as $$
51+
declare
52+
v_event text = TG_ARGV[0];
53+
v_topic text = TG_ARGV[1];
54+
begin
55+
perform pg_notify(v_topic, json_build_object(
56+
'event', v_event,
57+
'subject', new.id
58+
)::text);
59+
return new;
60+
end;
61+
$$ language plpgsql volatile set search_path from current;
62+
63+
64+
-- Core Ledger Acounting
65+
create table scaledger_public.accounts (
66+
id uuid primary key default uuid_generate_v4(),
67+
created_at timestamp default now(),
68+
updated_at timestamp default now(),
69+
type scaledger_public.account_type not null,
70+
name text not null check (char_length(name) < 255),
71+
code text,
72+
metadata jsonb
73+
);
74+
75+
create unique index on scaledger_public.accounts (code);
76+
77+
create trigger account_updated_at before update
78+
on scaledger_public.accounts
79+
for each row
80+
execute procedure scaledger_private.set_updated_at();
81+
82+
comment on table scaledger_public.accounts is 'An account to which every posting belongs';
83+
comment on column scaledger_public.accounts.id is 'The primary unique identifier for the account';
84+
comment on column scaledger_public.accounts.created_at is 'The account’s time created';
85+
comment on column scaledger_public.accounts.updated_at is 'The account’s last updated time';
86+
comment on column scaledger_public.accounts.type is 'The account’s type';
87+
comment on column scaledger_public.accounts.name is 'The account’s name';
88+
comment on column scaledger_public.accounts.code is 'An alphanumeric account code to identify the account';
89+
comment on column scaledger_public.accounts.metadata is 'A dictionary of arbitrary key-values';
90+
91+
92+
create table scaledger_public.postings (
93+
id uuid primary key default uuid_generate_v4(),
94+
credit_id uuid not null references scaledger_public.accounts(id) on delete restrict,
95+
debit_id uuid not null references scaledger_public.accounts(id) on delete restrict,
96+
currency scaledger_public.currency not null,
97+
created_at timestamp default now(),
98+
amount bigint not null check(amount <= 9007199254740991 AND amount >= 0), -- @note to support JavaScripts 2^53 - 1 maximum value
99+
external_id text not null,
100+
metadata jsonb
101+
);
102+
103+
create unique index on scaledger_public.postings (external_id);
104+
create index on scaledger_public.postings (amount);
105+
create index on scaledger_public.postings (credit_id);
106+
create index on scaledger_public.postings (debit_id);
107+
create index on scaledger_public.postings (currency);
108+
create index on scaledger_public.postings (created_at);
109+
110+
create trigger posting_created
111+
after insert on scaledger_public.postings
112+
for each row
113+
execute procedure scaledger_public.graphql_subscription(
114+
'posting_created', -- "event"
115+
'graphql:posting' -- "topic"
116+
);
117+
118+
comment on table scaledger_public.postings is 'A record of a credit/debt (amount) on an account';
119+
comment on column scaledger_public.postings.id is 'The primary unique identifier for the posting';
120+
comment on column scaledger_public.postings.credit_id is 'The account to which the amount is credited';
121+
comment on column scaledger_public.postings.debit_id is 'The account to which the amount is debited';
122+
comment on column scaledger_public.postings.currency is 'The currency the amount is denominated in';
123+
comment on column scaledger_public.postings.created_at is 'The posting’s time created';
124+
comment on column scaledger_public.postings.amount is 'The amount (credit/debit) applied in the transaction';
125+
comment on column scaledger_public.postings.metadata is 'A dictionary of arbitrary key-values';
126+
127+
-- VIEWs
128+
129+
create view scaledger_public.ledger as
130+
select account_ledger.*, accounts.name as "account_name" from (
131+
select sum(account_balances.balance) as "balance", account_balances.account_id, account_balances.currency from (
132+
select
133+
sum(postings.amount) as "balance",
134+
postings.credit_id as "account_id",
135+
postings.currency
136+
from scaledger_public.postings
137+
group by postings.credit_id, postings.currency
138+
union
139+
select
140+
sum(postings.amount * -1) as "balance",
141+
postings.debit_id as "account_id",
142+
postings.currency
143+
from scaledger_public.postings
144+
group by postings.debit_id, postings.currency
145+
) as account_balances
146+
group by account_balances.account_id, account_balances.currency
147+
) as account_ledger
148+
left join scaledger_public.accounts on account_ledger.account_id = accounts.id;
149+
150+
comment on view scaledger_public.ledger is 'A global ledger view of all accounts';
151+
152+
153+
commit;

‎src/db/scripts/bench.sql

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
begin;
2+
3+
insert into postings(amount, credit, debit) values
4+
(1000000, '62601a1e-d8f5-11e9-98ac-9f3daf8e2dd1', '62601a1e-d8f5-11e9-98ac-9f3daf8e2dd2');
5+
6+
end;

‎src/db/scripts/drop.sql

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- This script will delete everything created in `schema.sql`. This script is
2+
-- also idempotent, you can run it as many times as you would like. Nothing
3+
-- will be dropped if the schemas and roles do not exist.
4+
5+
begin;
6+
7+
drop schema if exists public, scaledger_public, scaledger_hidden, scaledger_private cascade;
8+
drop role if exists anonymous;
9+
drop role if exists signed_in;
10+
11+
commit;

‎src/db/scripts/schema

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
POSTGRES_HOST="${POSTGRES_HOST:-localhost}"
4+
POSTGRES_USER="${POSTGRES_USER:-scaledger}"
5+
POSTGRES_DB="${POSTGRES_DB:-scaledger}"
6+
7+
echo 'building schema'
8+
9+
set -e
10+
11+
psql -h $POSTGRES_HOST -U $POSTGRES_USER -v "ON_ERROR_STOP=1" $POSTGRES_DB <<OMG
12+
\i drop.sql
13+
14+
\i ../schema/schema.sql
15+
OMG
16+
17+
#eof

‎src/db/scripts/seed

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
4+
POSTGRES_HOST="${POSTGRES_HOST:-localhost}"
5+
POSTGRES_USER="${POSTGRES_USER:-scaledger}"
6+
POSTGRES_DB="${POSTGRES_DB:-scaledger}"
7+
8+
echo 'rebuilding schema'
9+
10+
set -e
11+
12+
./schema
13+
14+
psql -h $POSTGRES_HOST -U $POSTGRES_USER -v "ON_ERROR_STOP=1" $POSTGRES_DB -f ../seeds/seed.sql $@

‎src/db/seeds/seed.sql

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
SET search_path TO scaledger_public, public;
2+
3+
begin;
4+
5+
6+
insert into accounts (name, type) values
7+
('Founders', 'equity'),
8+
('Cash', 'asset'),
9+
('Accounts Payable', 'liability'),
10+
('Accounts Receivable', 'asset');
11+
12+
insert into postings(external_id, amount, credit_id, debit_id, currency, metadata)
13+
select uuid_generate_v4() as external_id, floor(random()*(100000-500+1))+500 as amount, credit_id, debit_id, 'USD' as currency, '{"order_id": "123"}' as metadata
14+
from
15+
generate_series(1, 1000) as series,
16+
(select id::uuid from accounts) as credit_id (credit_id),
17+
(select id::uuid from accounts) as debit_id (debit_id)
18+
where credit_id != debit_id;
19+
20+
21+
commit;

‎src/server/.dockerignore

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# .dockerignore
2+
.env
3+
.git
4+
.github
5+
.next
6+
.vscode
7+
node_modules
8+
9+
*Dockerfile*
10+
*docker-compose*
11+
12+
**/dist
13+
**/__tests__

‎src/server/Dockerfile

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Dockerfile
2+
3+
# Global args, set before the first FROM, shared by all stages
4+
ARG NODE_ENV="production"
5+
6+
################################################################################
7+
# Build stage 1 - `npm install`
8+
9+
FROM node:12-alpine as builder
10+
# Import our shared args
11+
ARG NODE_ENV
12+
13+
# Cache node_modules for as long as possible
14+
COPY package.json package-lock.json /app/
15+
WORKDIR /app/
16+
RUN npm install --production=false --no-progress
17+
18+
# Copy over the server source code
19+
20+
COPY src/ /app/src
21+
22+
# Run the build
23+
COPY tsconfig.json postgraphile.tags.json5 /app/
24+
RUN npm run build
25+
26+
################################################################################
27+
# Build stage 2 - COPY the relevant things (multiple steps)
28+
29+
FROM node:12-alpine as clean
30+
# Import our shared args
31+
ARG NODE_ENV
32+
33+
# Copy over selectively just the tings we need, try and avoid the rest
34+
COPY --from=builder /app/package.json /app/package-lock.json /app/postgraphile.tags.json5 /app/
35+
COPY --from=builder /app/dist/ /app/dist/
36+
37+
################################################################################
38+
# Build stage FINAL - COPY everything, once, and then do a clean `npm install`
39+
40+
FROM node:12-alpine
41+
# Import our shared args
42+
ARG NODE_ENV
43+
44+
EXPOSE 5000
45+
WORKDIR /app/
46+
# Copy everything from stage 2, it's already been filtered
47+
COPY --from=clean /app/ /app/
48+
49+
# Install deps
50+
RUN npm install --production=true --no-progress
51+
52+
LABEL description="An accounting ledger for developers"
53+
54+
# You might want to disable GRAPHILE_TURBO if you have issues
55+
ENV GRAPHILE_TURBO=1
56+
ENV NODE_ENV=$NODE_ENV
57+
ENTRYPOINT npm run start

‎src/server/package-lock.json

+1,835
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/server/package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "scaledger",
3+
"version": "1.0.0",
4+
"description": "A radically minimal double entry accounting system built with Postgresql, inspired by ledger",
5+
"repository": "https://gitlab.com/df8org/scaledger",
6+
"main": "src/app.ts",
7+
"scripts": {
8+
"build": "tsc -p tsconfig.json",
9+
"dev": "ts-node-dev --respawn .",
10+
"start": "ts-node ."
11+
},
12+
"dependencies": {
13+
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
14+
"@graphile/pg-pubsub": "^4.5.0",
15+
"dotenv": "^8.2.0",
16+
"express": "^4.17.1",
17+
"graphile-utils": "^4.5.6",
18+
"postgraphile": "^4.5.5"
19+
},
20+
"devDependencies": {
21+
"@types/express": "^4.17.2",
22+
"@types/node": "^12.12.21",
23+
"ts-node-dev": "^1.0.0-pre.44",
24+
"typescript": "^3.7.3"
25+
}
26+
}

‎src/server/postgraphile.tags.json5

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
version: 1,
3+
config: {
4+
/*
5+
* There can be entries here for:
6+
*
7+
* - `class`: for tables, composite types, views and materialized views
8+
* - `attribute`: for columns/attributes (of any 'class' type)
9+
* - `constraint`: for table constraints
10+
* - `procedure`: for functions/procedures
11+
*/
12+
class: {
13+
accounts: {
14+
tags: {
15+
omit: "delete",
16+
},
17+
},
18+
postings: {
19+
tags: {
20+
omit: "update,delete",
21+
},
22+
},
23+
},
24+
attribute: {
25+
id: {
26+
tags: {
27+
omit: "create,update",
28+
},
29+
},
30+
created_at: {
31+
tags: {
32+
omit: "create,update",
33+
},
34+
},
35+
updated_at: {
36+
tags: {
37+
omit: "create,update",
38+
}
39+
}
40+
},
41+
},
42+
}

‎src/server/src/app.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require('dotenv').config()
2+
3+
import express from "express";
4+
import postgrahile from "./middleware/postgraphile";
5+
6+
const app = express();
7+
8+
async function main() {
9+
await postgrahile(app);
10+
11+
app.listen(process.env.PORT);
12+
}
13+
14+
main();
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { postgraphile, makePluginHook } from "postgraphile";
2+
import { TagsFilePlugin } from "postgraphile/plugins";
3+
import simplifyInflector from "@graphile-contrib/pg-simplify-inflector";
4+
import forceBigInt from "../plugins/force-big-int";
5+
import e from "express";
6+
import subscriptions from "../plugins/subscriptions";
7+
import PgPubsub from "@graphile/pg-pubsub";
8+
9+
const pluginHook = makePluginHook([PgPubsub]);
10+
11+
const postgraphileOptions = (app: e.Application) => {
12+
return {
13+
appendPlugins: [simplifyInflector, forceBigInt, subscriptions, TagsFilePlugin],
14+
watchPg: true,
15+
dynamicJson: true,
16+
enhanceGraphiql: true,
17+
graphiql: true,
18+
sortExport: true,
19+
// @TODO make this automatically commit
20+
exportGqlSchemaPath: process.env.BUILD_SCHEMA ? `${__dirname}/../../src/schema.graphql` : null,
21+
retryOnInitFail: true,
22+
subscriptions: true,
23+
pluginHook
24+
}
25+
};
26+
27+
export default async (app: e.Application) => {
28+
app.use(postgraphile(
29+
process.env.DATABASE_URL,
30+
"scaledger_public",
31+
postgraphileOptions(app)
32+
));
33+
};
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @ts-ignore
2+
export default (builder) => {
3+
// @ts-ignore
4+
builder.hook("build", build => {
5+
// Dangerously force BigInt into Int - BigInt columns must have 2^53-1 limit constraints to work with JavaScript
6+
build.pgRegisterGqlTypeByTypeId(
7+
"20",
8+
() => build.graphql.GraphQLInt
9+
);
10+
return build;
11+
});
12+
};
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { makeExtendSchemaPlugin, gql } from "graphile-utils";
2+
3+
export default makeExtendSchemaPlugin(({ pgSql: sql }) => ({
4+
typeDefs: gql`
5+
type PostingCreatedSubscriptionPayload {
6+
# This is populated by our resolver below
7+
posting: Posting
8+
9+
# This is returned directly from the PostgreSQL subscription payload (JSON object)
10+
event: String
11+
}
12+
13+
extend type Subscription {
14+
postingCreated: PostingCreatedSubscriptionPayload @pgSubscription(topic: "posting_created")
15+
}
16+
`,
17+
18+
resolvers: {
19+
PostingCreatedSubscriptionPayload: {
20+
// This method finds the user from the database based on the event
21+
// published by PostgreSQL.
22+
//
23+
// In a future release, we hope to enable you to replace this entire
24+
// method with a small schema directive above, should you so desire. It's
25+
// mostly boilerplate.
26+
async posting(
27+
event,
28+
_args,
29+
_context,
30+
{ graphile: { selectGraphQLResultFromTable } }
31+
) {
32+
const rows = await selectGraphQLResultFromTable(
33+
sql.fragment`scaledger_public.postings`,
34+
(_tableAlias, sqlBuilder) => {
35+
sqlBuilder.where(
36+
sql.fragment`${sqlBuilder.getTableAlias()}.id = ${sql.value(
37+
event.subject
38+
)}`
39+
);
40+
}
41+
);
42+
return rows[0];
43+
},
44+
},
45+
},
46+
}));

‎src/server/src/schema.graphql

+1,059
Large diffs are not rendered by default.

‎src/server/src/schema.json

+5,756
Large diffs are not rendered by default.

‎src/server/tsconfig.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonjs",
4+
"esModuleInterop": true,
5+
"target": "es6",
6+
"noImplicitAny": true,
7+
"moduleResolution": "node",
8+
"sourceMap": true,
9+
"outDir": "dist",
10+
"baseUrl": ".",
11+
"paths": {
12+
"*": [
13+
"node_modules/*"
14+
]
15+
}
16+
},
17+
"include": [
18+
"src"
19+
]
20+
}

0 commit comments

Comments
 (0)
Please sign in to comment.