Skip to content

Commit c05e4ed

Browse files
committed
feat(@sirutils/seql): initialize package and added basic query features
1 parent 20e43c7 commit c05e4ed

17 files changed

+436
-0
lines changed

bun.lockb

384 Bytes
Binary file not shown.

packages/seql/moon.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
language: "typescript"
2+
type: "library"
3+
platform: "bun"
4+
5+
tasks:
6+
build:
7+
command: "bun x builder build src/index.ts -a"
8+
deps:
9+
- "seql:clean"
10+
- "builder:build"
11+
- "core:build"
12+
inputs:
13+
- "src/**/*"
14+
- "types/**/*"
15+
outputs:
16+
- "dist/**"
17+
watch:
18+
command: "bun x builder build src/index.ts -a -w"
19+
deps:
20+
- "seql:clean"
21+
- "builder:build"
22+
- "core:build"
23+
local: true
24+
inputs:
25+
- "src/**/*"
26+
- "types/**/*"

packages/seql/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@sirutils/seql",
3+
"version": "0.0.1",
4+
"type": "module",
5+
6+
"main": "dist/index.js",
7+
"module": "dist/index.js",
8+
"files": ["dist", "definitions"],
9+
"types": "dist/index.d.ts",
10+
11+
"devDependencies": {
12+
"@sirutils/builder": "workspace:*"
13+
},
14+
"dependencies": {
15+
"@sirutils/core": "workspace:*"
16+
}
17+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/// <reference types="@sirutils/core" />
2+
3+
import './seql'

packages/seql/src/definitions/seql.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { BlobType } from '@sirutils/core'
2+
3+
import type { SeqlTags } from '../tag'
4+
5+
declare global {
6+
// biome-ignore lint/style/noNamespace: Redundant
7+
namespace Sirutils {
8+
interface CustomErrors {
9+
seql: SeqlTags
10+
}
11+
12+
// biome-ignore lint/style/noNamespace: Redundant
13+
namespace Seql {
14+
export type ValueRecord<T = BlobType> = Record<string, T>
15+
16+
export interface Query<T = BlobType> {
17+
$type: symbol
18+
text: string
19+
values: T[]
20+
21+
builder: BuildedQuery<T>
22+
}
23+
24+
export interface BuildedQuery<T = BlobType> {
25+
$type: symbol
26+
values: T[]
27+
buildText(nextParamID: number): string
28+
}
29+
30+
export interface AdapterOptions {
31+
paramterPattern: (str: string) => string
32+
}
33+
34+
export interface Env {
35+
adaptor: 'mysql' | 'postgres'
36+
}
37+
}
38+
}
39+
}

packages/seql/src/index.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import './definitions'
2+
3+
import { buildAll, query } from './seql'
4+
5+
// biome-ignore lint/style/noNamespaceImport: For re-export as Seql
6+
import * as builder from './utils/builder'
7+
// biome-ignore lint/style/noNamespaceImport: For re-export as Seql
8+
import * as common from './utils/common'
9+
// biome-ignore lint/style/noNamespaceImport: For re-export as Seql
10+
import * as operations from './utils/operations'
11+
12+
export * from './tag'
13+
14+
export const Seql = {
15+
query,
16+
buildAll,
17+
18+
...builder,
19+
...common,
20+
...operations,
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const mysqlAdapter: Sirutils.Seql.AdapterOptions = {
2+
paramterPattern: () => '?',
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const postgresAdapter: Sirutils.Seql.AdapterOptions = {
2+
paramterPattern: str => `$${str}`,
3+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ENV } from './env'
2+
3+
import { mysqlAdapter } from './adapters/mysql'
4+
import { postgresAdapter } from './adapters/postgres'
5+
6+
export const adaptors = {
7+
postgres: postgresAdapter,
8+
mysql: mysqlAdapter,
9+
} as const
10+
11+
export const selectedAdaptor = adaptors[ENV.adaptor]

packages/seql/src/internal/consts.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { extractEnvs, unwrap } from '@sirutils/core'
2+
3+
export const ENV = unwrap(
4+
extractEnvs<Sirutils.Seql.Env>(env => ({
5+
adaptor: env.SEQL_ADAPTOR || 'mysql',
6+
}))
7+
)
8+
9+
export const Raw = Symbol('Raw Query')
10+
export const Generated = Symbol('Generated Query')

packages/seql/src/seql.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { BlobType } from '@sirutils/core'
2+
3+
import { generate, join, raw, toSqlBuilder } from './utils/builder'
4+
import { mergeLists } from './utils/common'
5+
6+
/**
7+
* Use this for creating queries
8+
*/
9+
export const query = (texts: TemplateStringsArray, ...values: BlobType[]): Sirutils.Seql.Query => {
10+
const result = generate(buildAll(texts, ...values))
11+
12+
return result
13+
}
14+
15+
/**
16+
* Internal template string tag that builds all props.
17+
*/
18+
export const buildAll = (
19+
texts: TemplateStringsArray,
20+
...values: BlobType[]
21+
): Sirutils.Seql.BuildedQuery => {
22+
const textSqlBuilders = texts.map(raw)
23+
const valueSqlBuilders = values.map(toSqlBuilder)
24+
25+
const sqlBuilders = mergeLists(textSqlBuilders, valueSqlBuilders)
26+
27+
return join(sqlBuilders)
28+
}

packages/seql/src/tag.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { tagBuilder } from '@sirutils/core'
2+
3+
const createTag = tagBuilder('@sirutils/seql')
4+
5+
export const seqlTags = {
6+
env: createTag('invalid-env'),
7+
} as const
8+
9+
export type SeqlTags = (typeof seqlTags)[keyof typeof seqlTags]

packages/seql/src/utils/builder.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { BlobType } from '@sirutils/core'
2+
3+
import { ENV } from '../internal/consts'
4+
import { adaptors } from '../internal/adaptors'
5+
import { Generated, Raw } from '../internal/consts'
6+
7+
/**
8+
* Base builder
9+
*/
10+
const builder = <T = BlobType>(
11+
values: T[],
12+
buildText: (nextParamID: number) => string
13+
): Sirutils.Seql.BuildedQuery<T> => {
14+
return {
15+
$type: Raw,
16+
values,
17+
buildText,
18+
}
19+
}
20+
21+
/**
22+
* Check is builder
23+
*/
24+
export const isBuilder = (builder: BlobType): builder is Sirutils.Seql.BuildedQuery => {
25+
return builder && builder.$type === Raw
26+
}
27+
28+
/**
29+
* Generate the full query
30+
*/
31+
export const generate = <T>(builder: Sirutils.Seql.BuildedQuery<T>): Sirutils.Seql.Query<T> => {
32+
return {
33+
$type: Generated,
34+
text: builder.buildText(1),
35+
values: builder.values,
36+
37+
builder,
38+
}
39+
}
40+
41+
/**
42+
* Check is generated
43+
*/
44+
export const isGenerated = (query: BlobType): query is Sirutils.Seql.Query => {
45+
return query && query.$type === Generated
46+
}
47+
48+
export const join = <T>(
49+
builders: Sirutils.Seql.BuildedQuery<T>[],
50+
delimiter = ''
51+
): Sirutils.Seql.BuildedQuery<T> => {
52+
return builder(
53+
builders.reduce((acc, { values }) => acc.concat(values), [] as T[]),
54+
nextParamID => {
55+
let paramID = nextParamID
56+
const builtText: string[] = []
57+
58+
for (const builder of builders) {
59+
builtText.push(builder.buildText(paramID))
60+
paramID += builder.values.length
61+
}
62+
63+
return builtText.join(delimiter)
64+
}
65+
)
66+
}
67+
68+
/**
69+
* Check if provided value is a BuildedQuery
70+
*/
71+
export const toSqlBuilder = <T>(
72+
value: Sirutils.Seql.BuildedQuery<T> | T
73+
): Sirutils.Seql.BuildedQuery<T> => {
74+
return isBuilder(value) ? value : safe(value)
75+
}
76+
77+
/**
78+
* Use for query (dangerous). use this function carefully
79+
*/
80+
export const raw = (value: string): Sirutils.Seql.BuildedQuery<string> => {
81+
return builder([], () => value)
82+
}
83+
84+
/**
85+
* Use for parameters.
86+
*/
87+
export const safe = <T>(value: T): Sirutils.Seql.BuildedQuery<T> => {
88+
return builder([value], nextParamID =>
89+
adaptors[ENV.adaptor].paramterPattern(nextParamID.toString())
90+
)
91+
}

packages/seql/src/utils/common.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { BlobType } from '@sirutils/core'
2+
3+
import { isGenerated, join } from './builder'
4+
5+
export const escapeIdentifier = (identifier: string) => {
6+
let result = identifier
7+
8+
if (result.charAt(0) === '"' && result.charAt(result.length - 1) === '"') {
9+
result = result.slice(1, result.length - 2)
10+
}
11+
if (result.includes('"')) {
12+
throw new Error(`Invalid identifier: ${result}`)
13+
}
14+
return `"${result}"`
15+
}
16+
17+
export const filterUndefined = <Value = BlobType>(
18+
object: Sirutils.Seql.ValueRecord<Value>
19+
): Sirutils.Seql.ValueRecord<Value> => {
20+
const filteredEntries = Object.entries(object).filter(([_, v]) => v !== undefined)
21+
22+
return Object.fromEntries(filteredEntries)
23+
}
24+
25+
export const mergeLists = <T>(list1: readonly T[], list2: readonly T[]): T[] => {
26+
const result: T[] = []
27+
28+
for (let i = 0; i < Math.max(list1.length, list2.length); i++) {
29+
const elem1 = list1[i]
30+
if (elem1 !== undefined) {
31+
result.push(elem1)
32+
}
33+
34+
const elem2 = list2[i]
35+
if (elem2 !== undefined) {
36+
const generated = (elem2 as BlobType).values
37+
.filter((value: BlobType) => isGenerated(value))
38+
.map((value: BlobType) => value.builder) as BlobType
39+
;(elem2 as BlobType).values = (elem2 as BlobType).values.filter(
40+
(value: BlobType) => !isGenerated(value)
41+
)
42+
43+
if (generated.length > 0) {
44+
result.push(join(generated) as T)
45+
} else {
46+
result.push(elem2)
47+
}
48+
}
49+
}
50+
51+
return result
52+
}
53+
54+
const areSetsEqual = <T>(set1: Set<T>, set2: Set<T>): boolean => {
55+
const difference = new Set(set1)
56+
for (const elem of set2) {
57+
if (difference.has(elem)) {
58+
difference.delete(elem)
59+
} else {
60+
return false
61+
}
62+
}
63+
64+
return difference.size === 0
65+
}
66+
67+
const objectKeys = (object: object): Set<string> => {
68+
return new Set(Object.keys(object))
69+
}
70+
71+
export const extractKeys = <Value = BlobType>(
72+
objects: Sirutils.Seql.ValueRecord<Value>[]
73+
): string[] => {
74+
if (objects.length === 0) {
75+
throw new Error('Cannot call extractKeys on empty list')
76+
}
77+
78+
// biome-ignore lint/style/noNonNullAssertion: <explanation>
79+
const keys = objectKeys(objects[0]!)
80+
81+
for (const o of objects) {
82+
const oKeys = objectKeys(o)
83+
if (!areSetsEqual(keys, oKeys)) {
84+
throw new Error(`Objects have different keys: ${JSON.stringify(objects)}`)
85+
}
86+
}
87+
88+
return Array.from(keys)
89+
}

0 commit comments

Comments
 (0)