Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/warm-elephants-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': patch
---

Added pg_hashids extension.
16 changes: 16 additions & 0 deletions docs/extensions/extensions.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,22 @@ const baseExtensions: Extension[] = [
importName: 'pg_ivm',
size: 24865,
},
{
name: 'pg_hashids',
description: `
Hashids is a small open-source library that generates short, unique, non-sequential
ids from numbers. It converts numbers like 347 into strings like “yr8”. You can also
decode those ids back. This is useful in bundling several parameters into one or simply
using them as short UIDs.
`,
shortDescription:
'Short unique id generator for PostgreSQL, using hashids.',
docs: 'https://github.com/iCyberon/pg_hashids',
tags: ['postgres extension'],
importPath: '@electric-sql/pglite/pg_hashids',
importName: 'pg_hashids',
size: 4212,
},
]

const tags = [
Expand Down
1 change: 1 addition & 0 deletions docs/repl/allExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export { tsm_system_time } from '@electric-sql/pglite/contrib/tsm_system_time'
export { unaccent } from '@electric-sql/pglite/contrib/unaccent'
export { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'
export { vector } from '@electric-sql/pglite/vector'
export { pg_hashids } from '@electric-sql/pglite/pg_hashids'
10 changes: 10 additions & 0 deletions packages/pglite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@
"types": "./dist/contrib/*.d.ts",
"import": "./dist/contrib/*.js",
"require": "./dist/contrib/*.cjs"
},
"./pg_hashids": {
"import": {
"types": "./dist/pg_hashids/index.d.ts",
"default": "./dist/pg_hashids/index.js"
},
"require": {
"types": "./dist/pg_hashids/index.d.cts",
"default": "./dist/pg_hashids/index.cjs"
}
}
},
"type": "module",
Expand Down
1 change: 1 addition & 0 deletions packages/pglite/scripts/bundle-wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ async function main() {
`require("./postgres.cjs").default`,
['.cjs'],
)
await findAndReplaceInDir('./dist/pg_hashids', /\.\.\/release\//g, '', ['.js', '.cjs'])
}

await main()
17 changes: 17 additions & 0 deletions packages/pglite/src/pg_hashids/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {
Extension,
ExtensionSetupResult,
PGliteInterface,
} from '../interface'

const setup = async (_pg: PGliteInterface, emscriptenOpts: any) => {
return {
emscriptenOpts,
bundlePath: new URL('../../release/pg_hashids.tar.gz', import.meta.url),
} satisfies ExtensionSetupResult
}

export const pg_hashids = {
name: 'pg_hashids',
setup,
} satisfies Extension
193 changes: 193 additions & 0 deletions packages/pglite/tests/pg_hashids.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { describe, it, expect } from 'vitest'
import { testEsmCjsAndDTC } from './test-utils.ts'

await testEsmCjsAndDTC(async (importType) => {
const { PGlite } =
importType === 'esm'
? await import('../dist/index.js')
: ((await import(
'../dist/index.cjs'
)) as unknown as typeof import('../dist/index.js'))

const { pg_hashids } =
importType === 'esm'
? await import('../dist/pg_hashids/index.js')
: ((await import(
'../dist/pg_hashids/index.cjs'
)) as unknown as typeof import('../dist/pg_hashids/index.js'))

describe(`pg_hashids`, () => {
it('can load extension', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.query<{ extname: string }>(`
SELECT extname
FROM pg_extension
WHERE extname = 'pg_hashids'
`)

expect(res.rows).toHaveLength(1)
expect(res.rows[0].extname).toBe('pg_hashids')
})

it('should return a hash using the default alphabet and empty salt', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(`SELECT id_encode(1001);`)

expect(res[0].rows[0].id_encode).toEqual('jNl')
})

it('should return a hash using the default alphabet and supplied salt', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(`SELECT id_encode(1234567, 'This is my salt');`)

expect(res[0].rows[0].id_encode).toEqual('Pdzxp')
})

it('should return a hash using the default alphabet, salt and minimum hash length', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_encode(1234567, 'This is my salt', 10);`,
)

expect(res[0].rows[0].id_encode).toEqual('PlRPdzxpR7')
})

it('should return a hash using the supplied alphabet, salt and minimum hash length', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_encode(1234567, 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`,
)

expect(res[0].rows[0].id_encode).toEqual('3GJ956J9B9')
})

it('should decode previously generated hash', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_decode('PlRPdzxpR7', 'This is my salt', 10);`,
)

expect(res[0].rows[0].id_decode).toEqual([1234567])
})

it('should decode previously generated hash using the supplied alphabet', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_decode('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`,
)

expect(res[0].rows[0].id_decode).toEqual([1234567])
})

it('should decode previously generated hash into a single integer', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(`SELECT id_decode_once('jNl');`)

expect(res[0].rows[0].id_decode_once).toEqual(1001)
})

it('should decode previously generated hash into a single integer using the supplied salt', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_decode_once('Pdzxp', 'This is my salt');`,
)

expect(res[0].rows[0].id_decode_once).toEqual(1234567)
})

it('should decode previously generated hash into a single integer using the supplied salt and minimum hash length', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_decode_once('PlRPdzxpR7', 'This is my salt', 10);`,
)

expect(res[0].rows[0].id_decode_once).toEqual(1234567)
})

it('should decode previously generated hash into a single integer using the supplied alphabet', async () => {
const pg = new PGlite({
extensions: {
pg_hashids,
},
})

await pg.exec('CREATE EXTENSION IF NOT EXISTS pg_hashids;')

const res = await pg.exec(
`SELECT id_decode_once('3GJ956J9B9', 'This is my salt', 10, 'abcdefghijABCDxFGHIJ1234567890');`,
)

expect(res[0].rows[0].id_decode_once).toEqual(1234567)
})
})
})
1 change: 1 addition & 0 deletions packages/pglite/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const entryPoints = [
'src/pgtap/index.ts',
'src/pg_uuidv7/index.ts',
'src/worker/index.ts',
'src/pg_hashids/index.ts',
]

const contribDir = path.join(root, 'src', 'contrib')
Expand Down
2 changes: 1 addition & 1 deletion postgres-pglite