@compilers/s2client
v1.0.2
Published
**SQL-first reactive queries for Vue 3**
Downloads
448
Readme
s2client
SQL-first reactive queries for Vue 3
Write SQL directly in your Vue components. No REST endpoints, no GraphQL schemas, no ORMs. Just SQL—encrypted at build time, executed server-side, fully reactive.
<script setup>
import { s2client } from '@compilers/s2client'
// One line. Fully reactive. Built-in loading/error states.
const users = s2client.query(`select * from users`).execute()
</script>
<template>
<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>
</template>Why s2client?
Traditional frontend development buries SQL behind layers of abstraction. You build REST endpoints, validate payloads, manage state manually, and write mountains of boilerplate. Backend teams create endpoints. Frontend teams wait. Everyone writes more code than necessary.
s2client eliminates the ceremony.
- ✅ Write SQL directly in Vue - No backend bottlenecks
- ✅ One line replaces dozens - No fetch calls, no manual state management
- ✅ Forms → Query parameters - Reactive objects bind directly to SQL
- ✅ Single backend endpoint - No endpoint sprawl, centralized security
- ✅ Build-time encryption - SQL encrypted with AES-256, never exposed in client
- ✅ Fully reactive - Query results are Vue refs, updates automatic
- ✅ Database agnostic - Works with PostgreSQL, MySQL, SQLite, SQL Server
The Problem
Traditional Frontend: ~40 lines of boilerplate
// ❌ Manual state management, loading, errors, fetch logic
const users = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchUsers() {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users')
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
const data = await response.json()
users.value = data
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
onMounted(() => fetchUsers())s2client: 1 line
// ✅ One line. Loading/error states built-in. Fully reactive.
const users = s2client.query(`select * from users`).execute()Installation
npm install @compilers/s2clientSetup
1. Configure Vite Plugin
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { dbbuilderTransform } from '@compilers/s2client/plugin'
export default defineConfig({
plugins: [
vue(),
dbbuilderTransform({
endpoint: '/api/query',
debounceMs: 300,
debug: false
})
]
})2. Create Backend Endpoint
// Example Express.js endpoint
app.post('/api/query', authenticate, async (req, res) => {
const { payload, params } = req.body
try {
// Decode the SQL (comes encrypted from frontend)
const sql = decodePayload(payload)
// Validate auth/permissions here
// Apply rate limiting if needed
// Execute query with parameterized values
const rows = await db.query(sql, params)
res.json({ rows })
} catch (error) {
res.status(400).json({ error: error.message })
}
})That's it. One endpoint handles all queries.
Examples
Read Data
<script setup>
// Automatic execution
const users = s2client.query(`select * from users`).execute()
// Or triggered execution
const orders = s2client.query(`select * from orders`)
</script>
<template>
<button @click="orders.execute()">Load Orders</button>
<div v-if="orders.loading">Loading...</div>
<div v-if="orders.error">{{ orders.error }}</div>
<div v-for="order in orders" :key="order.id">
{{ order.total }}
</div>
</template>Insert Data
<script setup>
const form = reactive({
name: '',
email: ''
})
const insertUser = s2client.query(`
insert into users (name, email)
values (:name, :email)
`, form)
function handleSubmit() {
insertUser.execute()
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" placeholder="Name" />
<input v-model="form.email" placeholder="Email" />
<button :disabled="insertUser.loading">
{{ insertUser.loading ? 'Saving...' : 'Submit' }}
</button>
<div v-if="insertUser.error">{{ insertUser.error }}</div>
</form>
</template>Update Data
<script setup>
const form = reactive({
id: null,
name: '',
email: ''
})
const updateUser = s2client.query(`
update users
set name = :name, email = :email
where id = :id
`, form)
// Form inputs update parameters automatically
// No need to gather data into payloads
</script>Delete Data
<script setup>
const userId = ref(null)
const deleteUser = s2client.query(`
delete from users where id = :id
`, { id: userId })
function handleDelete() {
if (confirm('Delete this user?')) {
deleteUser.execute()
}
}
</script>Reactive Queries
<script setup>
// Query re-runs automatically when searchTerm changes
const searchTerm = ref('')
const results = s2client.query(`
select * from products
where name like :search
`, {
search: computed(() => `%${searchTerm.value}%`)
}).execute()
</script>
<template>
<input v-model="searchTerm" placeholder="Search products..." />
<div v-for="product in results" :key="product.id">
{{ product.name }}
</div>
</template>Built-in Properties
Every query returns a reactive object with:
loading- Boolean, true during executionerror- Error message if query failsexecuted- Boolean, true after first executionlast_update- Timestamp of last successful executionexecute()- Method to manually run/re-run the queryreset()- Method to clear results and reset state
<script setup>
const users = s2client.query(`select * from users`).execute()
</script>
<template>
<div v-if="users.loading">Loading...</div>
<div v-if="users.error">Error: {{ users.error }}</div>
<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>
<button @click="users.execute()">Refresh</button>
<button @click="users.reset()">Clear</button>
<p>Last updated: {{ users.last_update }}</p>
</template>Security
s2client doesn't bypass security—it centralizes it.
Build-Time Security
- ✅ SQL encrypted with AES-256 at build time
- ✅ Compiler enforces parameterized queries only
- ✅ No executable SQL exists in client runtime
- ✅ Query structure hidden from client inspection
Runtime Security
You still control access server-side:
- ✅ Validate authentication/authorization
- ✅ Sanitize and validate query parameters
- ✅ Apply rate limiting and logging
- ✅ Use prepared statements (parameters always separate from SQL)
- ✅ Implement row-level security in your database
Security is applied once, in one place, instead of scattered across dozens of endpoints.
Guiding Principles
1. SQL is the Proven Abstraction
SQL is the language of data. It's established, powerful, and universal. QueryBuilders, ORMs, REST APIs, GraphQL—they all add unnecessary complexity. s2client lets you use SQL directly, as intended.
2. Less Code is Better Code
Every abstraction layer adds boilerplate. s2client eliminates mountains of frontend ceremony and backend endpoint sprawl. One line replaces dozens. Less code means fewer bugs, faster development, and easier maintenance.
3. Security First Development
SQL in the client is powerful but must be secured. s2client encrypts SQL at build time with AES-256, ensuring your queries remain private. The compiler enforces parameterized queries only. You still validate parameters server-side, but security is centralized in one place.
4. Intuitive Simple API
Method signatures are clean: 2-3 arguments max. s2client.query(sql, params) is all you need to know. No complex builders, no configuration hell, no mental overhead. The API is self-documenting.
5. Intuitive Reactivity
Query results are Vue refs that work seamlessly with watch(), watchEffect(), and templates. Change a parameter, the query re-runs automatically. No manual subscriptions, no callback hell. Reactivity just works.
6. Database Agnostic
s2client doesn't care what database you use. PostgreSQL, MySQL, SQLite, SQL Server—write standard SQL and your backend handles execution. Switch databases without changing frontend code.
Comparison
Before: Multiple endpoints for each resource
// ❌ Traditional Backend: 5 endpoints per resource
app.get('/api/users', async (req, res) => { /* ... */ })
app.get('/api/users/:id', async (req, res) => { /* ... */ })
app.post('/api/users', async (req, res) => { /* ... */ })
app.put('/api/users/:id', async (req, res) => { /* ... */ })
app.delete('/api/users/:id', async (req, res) => { /* ... */ })
// Orders - 5 more endpoints
// Products - 5 more endpoints
// That's 15 endpoints for 3 resources!After: One endpoint for everything
// ✅ s2client Backend: ONE endpoint for ALL queries
app.post('/api/query', authenticate, async (req, res) => {
const { payload, params } = req.body
const sql = decodePayload(payload)
const result = await db.query(sql, params)
res.json({ rows: result })
})
// That's it! Handles:
// - All user queries
// - All order queries
// - All product queries
// - ANY SQL operation
// Apply auth, rate limiting, logging in ONE placeFrontend = Fullstack
With s2client, frontend developers become fullstack:
- 🚀 Write SQL directly in Vue components
- 🚀 No waiting for backend teams to create endpoints
- 🚀 No context switching between frontend/backend
- 🚀 Ship features faster with less code
Requirements
- Node.js >= 18
- Vue 3.x
- Vite (build tool)
- Any SQL database (PostgreSQL, MySQL, SQLite, etc.)
License
MIT
Learn More
Build a lot with little code.
