1 Commits

Author SHA1 Message Date
Alessandro Magionami 3737ee06cd awilix integration 2024-09-09 17:56:33 +02:00
19 changed files with 310 additions and 232 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ services:
- PGPORT=5434
start_dependencies:
image: tehkapa/docker-wait-for-dependencies
image: ducktors/docker-wait-for-dependencies
depends_on:
- maindb
- eu_db
+2 -39
View File
@@ -1,43 +1,6 @@
export const mainDBConfig = {
export default {
client: 'pg',
connection: {
host: '127.0.0.1',
port: 5454,
user: 'speckle',
database: 'speckle_main',
password: 'speckle'
},
migrations: {
directory: 'src/migrations',
extension: 'ts'
}
}
export const euDBConfig = {
client: 'pg',
connection: {
host: '127.0.0.1',
port: 5455,
user: 'speckle',
database: 'speckle_eu',
password: 'speckle'
},
migrations: {
directory: 'src/migrations',
extension: 'ts'
}
}
export const usDBConfig = {
client: 'pg',
connection: {
host: '127.0.0.1',
port: 5456,
user: 'speckle',
database: 'speckle_us',
password: 'speckle'
},
connection: process.env.POSTGRES_URL,
migrations: {
directory: 'src/migrations',
extension: 'ts'
+4 -4
View File
@@ -6,19 +6,18 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "ts-standard",
"tsx": "docker compose up start_dependencies && tsx watch --clear-screen=false --conditions=typescript ./src/app.ts",
"tsx": "tsx",
"lint:fix": "ts-standard --fix",
"migration:make": "NODE_OPTIONS='--import tsx' knex migrate:make",
"migration:make": "NODE_OPTIONS='--loader ts-node/esm' knex migrate:make",
"dev:old": "nodemon --ext ts,graphql --exec node --inspect -r @swc/register src/bin/www.ts",
"build": "tsc",
"start": "node dist/app.js",
"dev": "nodemon src/app.ts"
"dev": "docker compose up start_dependencies && tsx src/app.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@ducktors/tsconfig": "^1.0.0",
"@types/node": "^20.11.13",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
@@ -31,6 +30,7 @@
},
"dependencies": {
"@apollo/server": "^4.10.0",
"awilix": "^11.0.0",
"dotenv": "^16.4.1",
"graphql": "^16.8.1",
"graphql-scalars": "^1.22.4",
+43 -19
View File
@@ -11,6 +11,9 @@ importers:
'@apollo/server':
specifier: ^4.10.0
version: 4.10.0(graphql@16.8.1)
awilix:
specifier: ^11.0.0
version: 11.0.0
dotenv:
specifier: ^16.4.1
version: 16.4.1
@@ -33,9 +36,6 @@ importers:
specifier: ^3.22.4
version: 3.22.4
devDependencies:
'@ducktors/tsconfig':
specifier: ^1.0.0
version: 1.0.0(typescript@5.3.3)
'@types/node':
specifier: ^20.11.13
version: 20.11.16
@@ -161,11 +161,6 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@ducktors/tsconfig@1.0.0':
resolution: {integrity: sha512-LmZNXZvGVnE+ITO3qj9MIlEkpVws3ZvgUK+eAAzkaiM2sIF/e41TvTtWG1jhMnzthTGhBb/pzKCGwgRK77a6SQ==}
peerDependencies:
typescript: ^5.2.2
'@esbuild/aix-ppc64@0.19.12':
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
engines: {node: '>=12'}
@@ -678,6 +673,10 @@ packages:
resolution: {integrity: sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==}
engines: {node: '>= 0.4'}
awilix@11.0.0:
resolution: {integrity: sha512-lnEm2TZu1OUWO1twi/3JrjSYu3RYjiOMiMQgfpwXj6uG4NSDtPSAWwTvqbpV7B8iWsXiC8GoMBKg1YsrUxPJhg==}
engines: {node: '>=16.3.0'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -717,6 +716,9 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -1069,10 +1071,6 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fastify-tsconfig@2.0.0:
resolution: {integrity: sha512-pvYwdtbZUJr/aTD7ZE0rGlvtYpx7IThHKVLBoqCKmT3FJpwm23XA2+PDmq8ZzfqqG4ajpyrHd5bkIixcIFjPhQ==}
engines: {node: '>=18.0.0'}
fastq@1.17.0:
resolution: {integrity: sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==}
@@ -1490,6 +1488,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -1562,6 +1563,9 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@@ -1674,6 +1678,9 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
@@ -2349,11 +2356,6 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@ducktors/tsconfig@1.0.0(typescript@5.3.3)':
dependencies:
fastify-tsconfig: 2.0.0
typescript: 5.3.3
'@esbuild/aix-ppc64@0.19.12':
optional: true
@@ -2875,6 +2877,11 @@ snapshots:
available-typed-arrays@1.0.6: {}
awilix@11.0.0:
dependencies:
camel-case: 4.1.2
fast-glob: 3.3.2
balanced-match@1.0.2: {}
binary-extensions@2.2.0: {}
@@ -2925,6 +2932,11 @@ snapshots:
callsites@3.1.0: {}
camel-case@4.1.2:
dependencies:
pascal-case: 3.1.2
tslib: 2.6.2
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -3426,8 +3438,6 @@ snapshots:
fast-levenshtein@2.0.6: {}
fastify-tsconfig@2.0.0: {}
fastq@1.17.0:
dependencies:
reusify: 1.0.4
@@ -3838,6 +3848,10 @@ snapshots:
dependencies:
js-tokens: 4.0.0
lower-case@2.0.2:
dependencies:
tslib: 2.6.2
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
@@ -3889,6 +3903,11 @@ snapshots:
negotiator@0.6.3: {}
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
tslib: 2.6.2
node-abort-controller@3.1.1: {}
node-fetch@2.7.0:
@@ -4013,6 +4032,11 @@ snapshots:
parseurl@1.3.3: {}
pascal-case@3.1.2:
dependencies:
no-case: 3.0.4
tslib: 2.6.2
path-exists@3.0.0: {}
path-exists@4.0.0: {}
+47 -39
View File
@@ -1,52 +1,60 @@
import { ApolloServer } from '@apollo/server'
import { resolvers } from './resolvers'
import { startStandaloneServer } from '@apollo/server/standalone'
import { readFileSync } from 'fs'
import { typeDefs as scalarTypeDefs } from 'graphql-scalars'
import { getDB } from './db'
import { Regions } from './regions'
import { initListeners } from './listeners'
import { ApolloServer } from "@apollo/server";
import { resolvers } from "./resolvers";
import { startStandaloneServer } from "@apollo/server/standalone";
import { readFileSync } from "fs";
import { typeDefs as scalarTypeDefs } from "graphql-scalars";
import { mainDb } from "./db";
import Knex, { Knex as KnexType } from 'knex'
import knexfile from "../knexfile";
import { container } from "./compositionRoot";
import { asValue } from "awilix";
const typeDefs = readFileSync('src/schema.graphql', { encoding: 'utf-8' })
const typeDefs = readFileSync("src/schema.graphql", { encoding: "utf-8" });
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
typeDefs: [typeDefs, ...scalarTypeDefs],
resolvers
})
resolvers,
});
const startServer = async (): Promise<void> => {
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 }
const plannedMigrations: Array<{ file: string }> = (
await mainDb.migrate.list()
)[1];
if (plannedMigrations.length > 0) {
console.log(
`🕰️ planning migrations: ${plannedMigrations
.map((m) => m.file)
.join(",")}`,
);
}
await mainDb.migrate.latest();
const regions = await mainDb('regions')
const regionalDBs = new Map<string, KnexType>(regions.map(region => [region.name, Knex({ ...knexfile, connection: region.connectionString })]))
await Promise.all(Array.from(regionalDBs.values()).map(db => db.migrate.latest())).catch(err => console.log({ err }))
container.register({
regionalDBs: asValue(regionalDBs),
})
const mainDB = getDB()
const regionDBs = Regions.map(region => getDB(region))
const databases = [mainDB, ...regionDBs]
for (const db of databases) {
const plannedMigrations: Array<{ file: string }> = (
await db.migrate.list()
)[1]
if (plannedMigrations.length > 0) {
console.log(
`🕰️ planning migrations: ${plannedMigrations
.map((m) => m.file)
.join(',')}`
)
}
await db.migrate.latest()
}
await initListeners()
console.log(`🚀 Server ready at: ${url}`)
}
const { url } = await startStandaloneServer(server, {
listen: { port: 3000 },
context: async ({ req }) => {
const scope = container.createScope(); // Create a scope per request
return {
container: scope,
// Add any other custom context, like user from req if needed
};
},
});
console.log(`🚀 Server ready at: ${url}`);
};
startServer()
.then()
.catch((err: Error) =>
console.log(`🔥 failed to start server ${err.message}`)
)
.catch((err: Error) => {
console.log({ err });
console.log(`🔥 failed to start server ${err.message}`);
});
+17
View File
@@ -0,0 +1,17 @@
import { asFunction, asValue, createContainer, InjectionMode } from 'awilix'
import { mainDb } from './db'
import { countComments, countResources, queryComments, queryResource, queryResourceAcl, queryUser } from './repositories'
export const container = createContainer({
injectionMode: InjectionMode.PROXY,
strict: true,
}).register({
mainDB: asValue(mainDb),
queryUser: asFunction(queryUser).scoped(),
queryResource: asFunction(queryResource).scoped(),
queryResourceAcl: asFunction(queryResourceAcl).scoped(),
countResources: asFunction(countResources).scoped(),
countComments: asFunction(countComments).scoped(),
queryComments: asFunction(queryComments).scoped(),
})
+3 -3
View File
@@ -2,8 +2,8 @@ import 'dotenv/config'
import { parseEnv } from 'znv'
import { z } from 'zod'
export const { POSTGRES_URL } = parseEnv(process.env, {
POSTGRES_URL: z.string().min(1)
const config = parseEnv(process.env, {
MAIN_DB_URI: z.string().min(1)
})
console.log([POSTGRES_URL].join(', '))
export default config
+8 -17
View File
@@ -1,19 +1,10 @@
import Knex, { Knex as KnexClient } from 'knex'
import { mainDBConfig, euDBConfig, usDBConfig } from '../knexfile'
import { Region } from './regions'
import Knex from 'knex'
import config from './config'
import knexfile from '../knexfile'
const mainDB = Knex(mainDBConfig)
const regionDBs = new Map<Region, KnexClient>([['eu', Knex(euDBConfig)], ['us', Knex(usDBConfig)]])
export function getDB(region?: Region) {
if (!region) {
return mainDB
}
const db = regionDBs.get(region)
if (!db) {
throw new Error('Region not supported')
}
return db
const knexConfig = {
...knexfile,
connection: config.MAIN_DB_URI,
}
export const mainDb = Knex(knexConfig)
-27
View File
@@ -1,27 +0,0 @@
import { Knex } from "knex";
import { getDB } from "./db";
import { Regions } from "./regions";
import { upsertResourceView } from "./repositories";
export async function initListeners() {
for (const region of Regions) {
const db = getDB(region)
const connection = await (db.client as Knex.Client).acquireRawConnection()
connection.query('LISTEN notifications')
connection.on('notification', async (data: { payload: string }) => {
console.log(JSON.parse(data.payload))
await upsertResourceView(region, JSON.parse(data.payload))
});
// connection.on('end', (err) => {
// reconnectClient(knex);
// })
connection.on('error', (err) => {
console.log(err);
})
}
}
@@ -1,16 +0,0 @@
import type { Knex } from 'knex'
const tableName = 'resource_views'
export async function up(knex: Knex): Promise<void> {
return await knex.schema.createTable(tableName, (table) => {
table.string('resourceId').primary()
table.string('region')
table.string('resourceName')
table.datetime('resourceCreatedAt')
})
}
export async function down(knex: Knex): Promise<void> {
return await knex.schema.dropTable(tableName)
}
@@ -0,0 +1,16 @@
import type { Knex } from "knex";
export async function up(knex: Knex): Promise<void> {
return await knex.schema.createTable('regions', (table) => {
table.text('id').primary()
table.text('name')
table.text('connectionString')
})
}
export async function down(knex: Knex): Promise<void> {
return await knex.schema.dropTable('regions')
}
@@ -0,0 +1,16 @@
import type { Knex } from "knex";
export async function up(knex: Knex): Promise<void> {
return await knex.schema.createTable('resource_meta', (table) => {
table.text('id').primary()
table.text('resourceId').references('id').inTable('resources')
table.text('region')
})
}
export async function down(knex: Knex): Promise<void> {
return await knex.schema.dropTable('resource_meta')
}
-2
View File
@@ -1,2 +0,0 @@
export type Region = 'eu' | 'us'
export const Regions: Region[] = ['eu', 'us']
+31 -40
View File
@@ -1,28 +1,27 @@
import { getDB } from "./db";
import { Region } from "./regions";
import { UserRecord, Resource, ResourceAcl, Comment, ResourceView } from "./types";
import { Knex } from "knex";
import { UserRecord, Resource, ResourceAcl, Comment, ResourceMeta } from "./types";
const Users = () => getDB()<UserRecord>("users");
const Resources = (region: Region) => getDB(region)<Resource>("resources");
const ResourceViews = () => getDB()<ResourceView>("resource_views");
const ResourceAclRepo = () => getDB()<ResourceAcl>("resource_acl");
const Comments = () => getDB()<Comment>("comments");
const tables = {
users: (db: Knex) => db<UserRecord>('users'),
resources: (db: Knex) => db<Resource>('resources'),
resourceAcl: (db: Knex) => db<ResourceAcl>('resource_acl'),
comments: (db: Knex) => db<Comment>('comments'),
resourceMeta: (db: Knex) => db<ResourceMeta>('resource_meta'),
}
export const queryUser = async (userId: string): Promise<UserRecord | null> => {
return (await Users().where("id", "=", userId).first()) ?? null;
export const queryUser = ({ mainDB }: { mainDB: Knex }) => async (userId: string): Promise<UserRecord | null> => {
return (await tables.users(mainDB).where("id", "=", userId).first()) ?? null;
};
export const queryResource = async (
export const queryResource = ({ mainDB, regionalDBs }: { mainDB: Knex, regionalDBs: Map<string, Knex> }) => async (
resourceId: string,
): Promise<Resource | null> => {
const resourceLocation = await ResourceViews().where('resourceId', '=', resourceId).first()
if (!resourceLocation?.region) {
return null
}
return await Resources(resourceLocation.region).where("id", "=", resourceId).first() || null;
const resourceMeta = await tables.resourceMeta(mainDB).where({ resourceId }).first()
const regionalDB = regionalDBs.get(resourceMeta!.region)!
return (await tables.resources(regionalDB).where("id", "=", resourceId).first()) ?? null;
};
export const queryResourceAcl = async ({
export const queryResourceAcl = ({ mainDB }: { mainDB: Knex }) => async ({
resourceId,
userId,
}: {
@@ -30,19 +29,19 @@ export const queryResourceAcl = async ({
userId: string;
}): Promise<ResourceAcl | null> => {
return (
(await ResourceAclRepo()
(await tables.resourceAcl(mainDB)
.where("userId", "=", userId)
.andWhere("resourceId", "=", resourceId)
.first()) ?? null
);
};
export const countResources = async (userId: string): Promise<number> => {
const [rawCount] = await ResourceAclRepo().count().where({ userId });
export const countResources = ({ mainDB }: { mainDB: Knex }) => async (userId: string): Promise<number> => {
const [rawCount] = await tables.resourceAcl(mainDB).count().where({ userId });
return parseInt(rawCount.count as string);
};
export const queryResources = async ({
export const queryResources = ({ mainDB, regionalDBs }: { mainDB: Knex; regionalDBs: Record<string, Knex> }) => async ({
userId,
limit,
cursor,
@@ -51,21 +50,23 @@ export const queryResources = async ({
limit: number;
cursor: string | null;
}) => {
const query = ResourceViews()
.join("resource_acl", "resource_views.resourceId", "resource_acl.resourceId")
const resourceMeta = await tables.resourceMeta(mainDB).where({ resourceId }).first()
const regionalDB = regionalDBs[resourceMeta!.region]
const query = Resources()
.join("resource_acl", "resources.id", "resource_acl.resourceId")
.where({ userId });
if (cursor) {
query.andWhere("resourceCreatedAt", "<", cursor);
query.andWhere("createdAt", "<", cursor);
}
return query.limit(limit);
return await query.limit(limit);
};
export const countComments = async (resourceId: string): Promise<number> => {
const [rawCount] = await Comments().count().where({ resourceId });
export const countComments = ({ mainDB }: { mainDB: Knex }) => async (resourceId: string): Promise<number> => {
const [rawCount] = await tables.comments(mainDB).count().where({ resourceId });
return parseInt(rawCount.count as string);
};
export const queryComments = async ({
export const queryComments = ({ mainDB }: { mainDB: Knex }) => async ({
resourceId,
limit,
cursor,
@@ -74,19 +75,9 @@ export const queryComments = async ({
limit: number;
cursor: string | null;
}): Promise<Comment[]> => {
const query = Comments().where({ resourceId });
const query = tables.comments(mainDB).where({ resourceId });
if (cursor) {
query.andWhere("createdAt", "<", cursor);
}
return query.limit(limit);
return await query.limit(limit);
};
export async function upsertResourceView(region: Region, resource: Resource) {
return ResourceViews().insert({
resourceId: resource.id,
resourceName: resource.name,
resourceCreatedAt: resource.createdAt,
region: region,
}).onConflict('resourceId')
.merge()
}
+8 -6
View File
@@ -1,8 +1,9 @@
import { queryResourceAcl } from "./repositories";
import { queryResource, queryResourceAcl } from "./repositories";
import { getUser, getResource, getComments, getResources } from "./services";
import { GraphQLError } from "graphql";
import {
Resource,
ResourceCollection,
UserRecord,
CommentCollection,
PaginationArgs,
@@ -12,14 +13,15 @@ import {
// This resolver retrieves books from the "books" array above.
export const resolvers = {
Query: {
async user(_: unknown, args: { id: string }) {
return await getUser(args.id);
async user(_: unknown, args: { id: string }, ctx) {
return await ctx.container.cradle.queryUser(args.id);
},
async resource(
_: unknown,
args: { id: string; userId: string },
ctx
): Promise<Resource> {
const maybeAcl = await queryResourceAcl({
const maybeAcl = await ctx.container.cradle.queryResourceAcl({
userId: args.userId,
resourceId: args.id,
});
@@ -33,7 +35,7 @@ export const resolvers = {
},
);
}
const maybeResource = await getResource(args.id);
const maybeResource = await ctx.container.cradle.queryResource(args.id);
if (maybeResource == null) {
throw new GraphQLError("Resource not found", {
extensions: { code: "RESOURCE_NOT_FOUND" },
@@ -47,7 +49,7 @@ export const resolvers = {
return await getResources({ userId: parent.id, ...args });
},
},
ResourceDetail: {
Resource: {
async comments(
parent: Resource,
{ limit, cursor }: PaginationArgs,
+1 -7
View File
@@ -15,12 +15,6 @@ type Resource {
id: String!
name: String!
createdAt: DateTime!
}
type ResourceDetail {
id: String!
name: String!
createdAt: DateTime!
comments(limit: Int! = 10, cursor: String = null): CommentCollection!
}
@@ -39,5 +33,5 @@ type User {
type Query {
user(id: String!): User
resource(id: String!): ResourceDetail
resource(id: String!, userId: String!): Resource
}
+2 -2
View File
@@ -15,11 +15,11 @@ import {
} from "./types";
export const getUser = async (id: string): Promise<UserRecord | null> => {
return queryUser(id);
return await queryUser(id);
};
export const getResource = async (id: string): Promise<Resource | null> => {
return queryResource(id);
return await queryResource(id);
};
interface GetResourcesArgs extends PaginationArgs {
+4 -6
View File
@@ -1,5 +1,3 @@
import { Region } from "./regions";
export interface Comment {
id: string;
userId: string;
@@ -25,6 +23,7 @@ export interface Resource {
id: string;
name: string;
createdAt: Date;
region: string
}
export interface ResourceCollection extends Collection<Resource> { }
@@ -47,9 +46,8 @@ export interface ResourceAcl {
resourceId: string;
}
export interface ResourceView {
region: Region;
export interface ResourceMeta {
id: string
region: string;
resourceId: string;
resourceName: string;
resourceCreatedAt: Date;
}
+107 -4
View File
@@ -1,6 +1,109 @@
{
"extends": "@ducktors/tsconfig",
"include": [
"**/*.ts"
]
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}