Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Add type declarations (resolve #46) #61

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Model, ModelClass, Page } from 'objection-2';
import { QueryBuilder } from 'knex';

export as namespace ObjectionHashID;

export interface HashProperties {
hashid: string;
hashId: string;
}

export class AuthQueryBuilder<M extends Model, R = M[]> {
ArrayQueryBuilderType: AuthQueryBuilder<M, M[]>;
SingleQueryBuilderType: AuthQueryBuilder<M, M>;
NumberQueryBuilderType: AuthQueryBuilder<M, number>;
PageQueryBuilderType: AuthQueryBuilder<M, Page<M>>;

findByHashId: (hashId: string) => this['SingleQueryBuilderType'] & M['QueryBuilderType']['SingleQueryBuilderType'];
}

export interface HashIdInstance<T extends typeof Model> {
QueryBuilderType: AuthQueryBuilder<this & T['prototype']>;

hashid: string;
hashId: string;
}

export interface HashIdStatic<T extends typeof Model> {
QueryBuilder: typeof AuthQueryBuilder;
hashIdSalt: string;
hashIdMinLength: number | void;
hashIdAlphabet: string | void;
hashIdSeps: string | void;
hashIdField: string | boolean;
hashedFields: Array<any>;

new (): HashIdInstance<T> & T['prototype'];
}

export default function hashid<T extends typeof Model>(model: T): HashIdStatic<T> & Omit<T, 'new'> & T['prototype'];
169 changes: 169 additions & 0 deletions index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import plugin from '.';
import {Model} from 'objection-2';
import knexjs from 'knex';

const knex = knexjs({
client: 'sqlite3',
connection: { filename: ':memory:' },
useNullAsDefault: true
});

Model.knex(knex)

class BaseModel extends plugin(Model) {
static get tableName () {
return 'table1'
}

id!: string;
}

class HiddenId extends BaseModel {
static hidden = ['id'];
static hashIdField = false;
}

class AlgoliaObject extends BaseModel {
static get hashIdField () {
return 'ObjectID'
}
}

class FatModel extends BaseModel {
static get hashedFields () {
return ['foo', 'bar']
}
}

class CompoundPK extends BaseModel {
static get tableName () {
return 'table2'
}

static get idColumn () {
return ['x', 'y']
}
}

class SubModel extends BaseModel {
static get hashIdSalt () {
return 'static'
}

static get hashIdMinLength () {
return 6
}
}

class ModelA extends SubModel {
static get relationMappings () {
return {
modelBs: {
relation: BaseModel.HasManyRelation,
modelClass: ModelB,
join: {
from: `${this.tableName}.id`,
to: `${ModelB.tableName}.fk_id`
}
}
}
}
}

class ModelB extends SubModel {
static get tableName () {
return 'table3'
}

static get hashedFields () {
return ['fk_id']
}
}

describe(`objection-hashid types (w/ objection v2)`, () => {
beforeAll(async () => {
await knex.schema.createTable(BaseModel.tableName, table => {
table.increments()
table.integer('foo')
})

await knex.schema.createTable(CompoundPK.tableName, table => {
table.integer('x').notNullable()
table.integer('y').notNullable()
table.primary(['x', 'y'])
})

await knex.schema.createTable(ModelB.tableName, table => {
table.increments()
table
.integer('fk_id')
.references(`${ModelA.tableName}.id`)
.notNullable()
})
})

test('fills out hashId', async () => {
const model = await BaseModel.query().insert({});

expect(typeof model.id).toBe('number')
expect(typeof model.hashId).toBe('string')
expect(model.hashId.length).toBeGreaterThan(0) // hashid returns blank string on error
})

test('aliases hashid', async () => {
const model = await BaseModel.query().first()

expect(model.hashid).toBeDefined()
})

test('writes hashid to resulting object', async () => {
const model = await BaseModel.query().first()

expect(typeof model.toJSON().id).toEqual('string')
})

test('can change what field the hashed PK is written under', async () => {
const model = await AlgoliaObject.query().first()

expect(typeof model.toJSON().ObjectID).toBe('string')
})

test('can hash other fields as well', async () => {
const model = await FatModel.query().insertAndFetch({ foo: 4 })

expect(typeof model.toJSON().foo).toBe('string')
})

let obj, hashId

test('works with objection-visibility', async () => {
const model = await HiddenId.query().insertAndFetch({})
obj = model.toJSON()
hashId = model.hashId

expect(obj.id).toBeUndefined()
expect(typeof obj.hashId).toBe('undefined')
})

test('search by hashId', async () => {
const model = await HiddenId.query().findByHashId(hashId)

expect(model).toBeTruthy()
})

test('works with compound primary keys', async () => {
const model = await CompoundPK.query().insertAndFetch({ x: 6, y: 9 })
const hashId = model.toJSON().id
expect(typeof hashId).toBe('string')

const instance = await CompoundPK.query().findByHashId(hashId)
expect(instance.$id()).toEqual([6, 9])
})

test('maintains reference across multiple models', async () => {
const modelA = await ModelA.query().insertAndFetch({})
const modelB = await modelA.$relatedQuery('modelBs').insert({})

expect(modelA.toJSON().id).toEqual(modelB.toJSON().fk_id)
})
})
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Jane Jeon <JaneJeon9719@gmail.com>",
"license": "MIT",
"scripts": {
"test": "jest",
"test": "jest index.test.js",
"test:watch": "yarn test --watch",
"test:ci": "yarn test --ci --reporters=default --reporters=jest-junit",
"lint": "standard | snazzy && prettier --check '**/*.{md,json,yml,yaml}'"
Expand Down Expand Up @@ -37,7 +37,9 @@
"lodash.memoize": "^4.1.2"
},
"devDependencies": {
"@types/jest": "^25.1.1",
"@types/jest": "^25.1.2",
"@types/mocha": "^7.0.1",
"@types/node": "^13.7.2",
"husky": "^4.2.1",
"jest": "^25.1.0",
"jest-junit": "^10.0.0",
Expand All @@ -49,7 +51,8 @@
"prettier": "^1.18.2",
"snazzy": "^8.0.0",
"sqlite3": "^4.1.0",
"standard": "^14.1.0"
"standard": "^14.1.0",
"tslint-no-unused-expression-chai": "^0.1.4"
},
"peerDependencies": {
"objection": "^1 || ^2"
Expand Down
16 changes: 16 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"lib": ["es6", "dom"],
"esModuleInterop": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"types": ["mocha", "jest"],

"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": ["index.d.ts", "index.test.ts"]
}
31 changes: 31 additions & 0 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"defaultSeverity": "error",
"extends": ["tslint:recommended", "tslint-no-unused-expression-chai"],
"jsRules": {},
"rules": {
"indent": [true, "tabs"],
"quotemark": [true, "single"],
"arrow-parens": [true, "ban-single-arg-parens"],
"whitespace": false,
"ordered-imports": false,
"interface-name": [true, "never-prefix"],
"trailing-comma": [true, { "multiline": "never", "singleline": "never" }],
"semicolon": [true, "always", "ignore-bound-class-methods"],
"object-literal-sort-keys": false,
"object-literal-key-quotes": [true, "as-needed"],
"max-line-length": false,
"max-classes-per-file": false,
"only-arrow-functions": false,
"variable-name": [
true,
"check-format",
"ban-keywords",
"allow-pascal-case",
"allow-leading-underscore"
]
},
"rulesDirectory": [],
"linterOptions": {
"exclude": ["node_modules"]
}
}
30 changes: 27 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -534,14 +534,24 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"

"@types/jest@^25.1.1":
"@types/jest@^25.1.2":
version "25.1.2"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.2.tgz#1c4c8770c27906c7d8def5d2033df9dbd39f60da"
resolved "https://registry.npmjs.org/@types/jest/-/jest-25.1.2.tgz#1c4c8770c27906c7d8def5d2033df9dbd39f60da"
integrity sha512-EsPIgEsonlXmYV7GzUqcvORsSS9Gqxw/OvkGwHfAdpjduNRxMlhsav0O5Kb0zijc/eXSO/uW6SJt9nwull8AUQ==
dependencies:
jest-diff "^25.1.0"
pretty-format "^25.1.0"

"@types/mocha@^7.0.1":
version "7.0.1"
resolved "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e"
integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==

"@types/node@^13.7.2":
version "13.7.2"
resolved "https://registry.npmjs.org/@types/node/-/node-13.7.2.tgz#50375b95b5845a34efda2ffb3a087c7becbc46c6"
integrity sha512-uvilvAQbdJvnSBFcKJ2td4016urcGvsiR+N4dHGU87ml8O2Vl6l+ErOi9w0kXSPiwJ1AYlIW+0pDXDWWMOiWbw==

"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
Expand Down Expand Up @@ -5105,11 +5115,25 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"

tslib@^1.9.0:
tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==

tslint-no-unused-expression-chai@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/tslint-no-unused-expression-chai/-/tslint-no-unused-expression-chai-0.1.4.tgz#f4a2c9dd3306088f44eb7574cf470082b09ade49"
integrity sha512-frEWKNTcq7VsaWKgUxMDOB2N/cmQadVkUtUGIut+2K4nv/uFXPfgJyPjuNC/cHyfUVqIkHMAvHOCL+d/McU3nQ==
dependencies:
tsutils "^3.0.0"

tsutils@^3.0.0:
version "3.17.1"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
dependencies:
tslib "^1.8.1"

tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
Expand Down