xansql 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Types/fields/Array.d.ts +7 -0
- package/Types/fields/Array.js +6 -0
- package/Types/fields/Array.js.map +1 -0
- package/Types/fields/Array.mjs +6 -0
- package/Types/fields/Array.mjs.map +1 -0
- package/Types/fields/Boolean.d.ts +8 -0
- package/Types/fields/Boolean.js +11 -0
- package/Types/fields/Boolean.js.map +1 -0
- package/Types/fields/Boolean.mjs +11 -0
- package/Types/fields/Boolean.mjs.map +1 -0
- package/Types/fields/Date.d.ts +10 -0
- package/Types/fields/Date.js +22 -0
- package/Types/fields/Date.js.map +1 -0
- package/Types/fields/Date.mjs +22 -0
- package/Types/fields/Date.mjs.map +1 -0
- package/Types/fields/Enum.d.ts +8 -0
- package/Types/fields/Enum.js +10 -0
- package/Types/fields/Enum.js.map +1 -0
- package/Types/fields/Enum.mjs +10 -0
- package/Types/fields/Enum.mjs.map +1 -0
- package/Types/fields/File.d.ts +7 -0
- package/Types/fields/File.js +6 -0
- package/Types/fields/File.js.map +1 -0
- package/Types/fields/File.mjs +6 -0
- package/Types/fields/File.mjs.map +1 -0
- package/Types/fields/IDField.d.ts +6 -0
- package/Types/fields/IDField.js +2 -0
- package/Types/fields/IDField.js.map +1 -0
- package/Types/fields/IDField.mjs +2 -0
- package/Types/fields/IDField.mjs.map +1 -0
- package/Types/fields/Number.d.ts +8 -0
- package/Types/fields/Number.js +11 -0
- package/Types/fields/Number.js.map +1 -0
- package/Types/fields/Number.mjs +11 -0
- package/Types/fields/Number.mjs.map +1 -0
- package/Types/fields/Object.d.ts +8 -0
- package/Types/fields/Object.js +11 -0
- package/Types/fields/Object.js.map +1 -0
- package/Types/fields/Object.mjs +11 -0
- package/Types/fields/Object.mjs.map +1 -0
- package/Types/fields/Record.d.ts +8 -0
- package/Types/fields/Record.js +11 -0
- package/Types/fields/Record.js.map +1 -0
- package/Types/fields/Record.mjs +11 -0
- package/Types/fields/Record.mjs.map +1 -0
- package/Types/fields/Schema.d.ts +16 -0
- package/Types/fields/Schema.js +34 -0
- package/Types/fields/Schema.js.map +1 -0
- package/Types/fields/Schema.mjs +34 -0
- package/Types/fields/Schema.mjs.map +1 -0
- package/Types/fields/String.d.ts +10 -0
- package/Types/fields/String.js +20 -0
- package/Types/fields/String.js.map +1 -0
- package/Types/fields/String.mjs +20 -0
- package/Types/fields/String.mjs.map +1 -0
- package/Types/fields/Tuple.d.ts +8 -0
- package/Types/fields/Tuple.js +11 -0
- package/Types/fields/Tuple.js.map +1 -0
- package/Types/fields/Tuple.mjs +11 -0
- package/Types/fields/Tuple.mjs.map +1 -0
- package/Types/fields/Union.d.ts +8 -0
- package/Types/fields/Union.js +11 -0
- package/Types/fields/Union.js.map +1 -0
- package/Types/fields/Union.mjs +11 -0
- package/Types/fields/Union.mjs.map +1 -0
- package/Types/index.d.ts +56 -0
- package/Types/index.js +129 -0
- package/Types/index.js.map +1 -0
- package/Types/index.mjs +129 -0
- package/Types/index.mjs.map +1 -0
- package/Types/types.d.ts +20 -0
- package/core/ExcuteMeta.d.ts +11 -0
- package/core/ExcuteMeta.js +22 -0
- package/core/ExcuteMeta.js.map +1 -0
- package/core/ExcuteMeta.mjs +22 -0
- package/core/ExcuteMeta.mjs.map +1 -0
- package/core/Xansql.d.ts +46 -0
- package/core/Xansql.js +132 -0
- package/core/Xansql.js.map +1 -0
- package/core/Xansql.mjs +132 -0
- package/core/Xansql.mjs.map +1 -0
- package/core/XansqlError.js +11 -0
- package/core/XansqlError.js.map +1 -0
- package/core/XansqlError.mjs +11 -0
- package/core/XansqlError.mjs.map +1 -0
- package/core/XansqlResult.d.ts +12 -0
- package/core/XansqlResult.js +32 -0
- package/core/XansqlResult.js.map +1 -0
- package/core/XansqlResult.mjs +32 -0
- package/core/XansqlResult.mjs.map +1 -0
- package/core/classes/EventManager.d.ts +72 -0
- package/core/classes/EventManager.js +21 -0
- package/core/classes/EventManager.js.map +1 -0
- package/core/classes/EventManager.mjs +21 -0
- package/core/classes/EventManager.mjs.map +1 -0
- package/core/classes/ForeignInfo.js +51 -0
- package/core/classes/ForeignInfo.js.map +1 -0
- package/core/classes/ForeignInfo.mjs +51 -0
- package/core/classes/ForeignInfo.mjs.map +1 -0
- package/core/classes/Migration/ForeingMigration.d.ts +12 -0
- package/core/classes/Migration/ForeingMigration.js +52 -0
- package/core/classes/Migration/ForeingMigration.js.map +1 -0
- package/core/classes/Migration/ForeingMigration.mjs +52 -0
- package/core/classes/Migration/ForeingMigration.mjs.map +1 -0
- package/core/classes/Migration/IndexMigration.d.ts +12 -0
- package/core/classes/Migration/IndexMigration.js +49 -0
- package/core/classes/Migration/IndexMigration.js.map +1 -0
- package/core/classes/Migration/IndexMigration.mjs +49 -0
- package/core/classes/Migration/IndexMigration.mjs.map +1 -0
- package/core/classes/Migration/TableMigration.d.ts +33 -0
- package/core/classes/Migration/TableMigration.js +215 -0
- package/core/classes/Migration/TableMigration.js.map +1 -0
- package/core/classes/Migration/TableMigration.mjs +215 -0
- package/core/classes/Migration/TableMigration.mjs.map +1 -0
- package/core/classes/Migration/index.d.ts +12 -0
- package/core/classes/Migration/index.js +189 -0
- package/core/classes/Migration/index.js.map +1 -0
- package/core/classes/Migration/index.mjs +189 -0
- package/core/classes/Migration/index.mjs.map +1 -0
- package/core/classes/ModelFormatter.js +166 -0
- package/core/classes/ModelFormatter.js.map +1 -0
- package/core/classes/ModelFormatter.mjs +166 -0
- package/core/classes/ModelFormatter.mjs.map +1 -0
- package/core/classes/TypesGenerator.d.ts +13 -0
- package/core/classes/TypesGenerator.js +170 -0
- package/core/classes/TypesGenerator.js.map +1 -0
- package/core/classes/TypesGenerator.mjs +170 -0
- package/core/classes/TypesGenerator.mjs.map +1 -0
- package/core/classes/XansqlConfig.js +33 -0
- package/core/classes/XansqlConfig.js.map +1 -0
- package/core/classes/XansqlConfig.mjs +33 -0
- package/core/classes/XansqlConfig.mjs.map +1 -0
- package/core/classes/XansqlFetch.js +304 -0
- package/core/classes/XansqlFetch.js.map +1 -0
- package/core/classes/XansqlFetch.mjs +304 -0
- package/core/classes/XansqlFetch.mjs.map +1 -0
- package/core/classes/XansqlTransaction.d.ts +13 -0
- package/core/classes/XansqlTransaction.js +46 -0
- package/core/classes/XansqlTransaction.js.map +1 -0
- package/core/classes/XansqlTransaction.mjs +46 -0
- package/core/classes/XansqlTransaction.mjs.map +1 -0
- package/core/type.d.ts +117 -0
- package/index.d.ts +3 -0
- package/index.js +1 -0
- package/index.js.map +1 -0
- package/index.mjs +1 -0
- package/index.mjs.map +1 -0
- package/model/Args/RelationExcuteArgs.js +5 -0
- package/model/Args/RelationExcuteArgs.js.map +1 -0
- package/model/Args/RelationExcuteArgs.mjs +5 -0
- package/model/Args/RelationExcuteArgs.mjs.map +1 -0
- package/model/Args/WhereArgs.js +226 -0
- package/model/Args/WhereArgs.js.map +1 -0
- package/model/Args/WhereArgs.mjs +226 -0
- package/model/Args/WhereArgs.mjs.map +1 -0
- package/model/Base.d.ts +26 -0
- package/model/Base.js +64 -0
- package/model/Base.js.map +1 -0
- package/model/Base.mjs +64 -0
- package/model/Base.mjs.map +1 -0
- package/model/Executer/Aggregate/SelectArgs.js +59 -0
- package/model/Executer/Aggregate/SelectArgs.js.map +1 -0
- package/model/Executer/Aggregate/SelectArgs.mjs +59 -0
- package/model/Executer/Aggregate/SelectArgs.mjs.map +1 -0
- package/model/Executer/Aggregate/index.js +59 -0
- package/model/Executer/Aggregate/index.js.map +1 -0
- package/model/Executer/Aggregate/index.mjs +59 -0
- package/model/Executer/Aggregate/index.mjs.map +1 -0
- package/model/Executer/Create/CreateDataArgs.js +145 -0
- package/model/Executer/Create/CreateDataArgs.js.map +1 -0
- package/model/Executer/Create/CreateDataArgs.mjs +145 -0
- package/model/Executer/Create/CreateDataArgs.mjs.map +1 -0
- package/model/Executer/Create/index.js +101 -0
- package/model/Executer/Create/index.js.map +1 -0
- package/model/Executer/Create/index.mjs +101 -0
- package/model/Executer/Create/index.mjs.map +1 -0
- package/model/Executer/Delete/index.js +112 -0
- package/model/Executer/Delete/index.js.map +1 -0
- package/model/Executer/Delete/index.mjs +112 -0
- package/model/Executer/Delete/index.mjs.map +1 -0
- package/model/Executer/Find/DistinctArgs.js +32 -0
- package/model/Executer/Find/DistinctArgs.js.map +1 -0
- package/model/Executer/Find/DistinctArgs.mjs +32 -0
- package/model/Executer/Find/DistinctArgs.mjs.map +1 -0
- package/model/Executer/Find/LimitArgs.js +31 -0
- package/model/Executer/Find/LimitArgs.js.map +1 -0
- package/model/Executer/Find/LimitArgs.mjs +31 -0
- package/model/Executer/Find/LimitArgs.mjs.map +1 -0
- package/model/Executer/Find/OrderByArgs.js +29 -0
- package/model/Executer/Find/OrderByArgs.js.map +1 -0
- package/model/Executer/Find/OrderByArgs.mjs +29 -0
- package/model/Executer/Find/OrderByArgs.mjs.map +1 -0
- package/model/Executer/Find/SelectArgs.js +119 -0
- package/model/Executer/Find/SelectArgs.js.map +1 -0
- package/model/Executer/Find/SelectArgs.mjs +119 -0
- package/model/Executer/Find/SelectArgs.mjs.map +1 -0
- package/model/Executer/Find/index.js +338 -0
- package/model/Executer/Find/index.js.map +1 -0
- package/model/Executer/Find/index.mjs +338 -0
- package/model/Executer/Find/index.mjs.map +1 -0
- package/model/Executer/Update/UpdateDataArgs.js +124 -0
- package/model/Executer/Update/UpdateDataArgs.js.map +1 -0
- package/model/Executer/Update/UpdateDataArgs.mjs +124 -0
- package/model/Executer/Update/UpdateDataArgs.mjs.map +1 -0
- package/model/Executer/Update/index.js +207 -0
- package/model/Executer/Update/index.js.map +1 -0
- package/model/Executer/Update/index.mjs +207 -0
- package/model/Executer/Update/index.mjs.map +1 -0
- package/model/include/ValueFormatter.js +99 -0
- package/model/include/ValueFormatter.js.map +1 -0
- package/model/include/ValueFormatter.mjs +99 -0
- package/model/include/ValueFormatter.mjs.map +1 -0
- package/model/index.d.ts +29 -0
- package/model/index.js +236 -0
- package/model/index.js.map +1 -0
- package/model/index.mjs +236 -0
- package/model/index.mjs.map +1 -0
- package/model/type.d.ts +106 -0
- package/package.json +32 -0
- package/readme.md +359 -0
- package/utils/chunker.js +53 -0
- package/utils/chunker.js.map +1 -0
- package/utils/chunker.mjs +53 -0
- package/utils/chunker.mjs.map +1 -0
- package/utils/index.js +49 -0
- package/utils/index.js.map +1 -0
- package/utils/index.mjs +49 -0
- package/utils/index.mjs.map +1 -0
- package/utils/sha256.js +66 -0
- package/utils/sha256.js.map +1 -0
- package/utils/sha256.mjs +66 -0
- package/utils/sha256.mjs.map +1 -0
package/readme.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# xansql
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Type-safe, event-driven SQL ORM with automatic schema synchronization, composable relations, granular hooks, and optional client execution bridge.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<!-- Badges (replace placeholders when public) -->
|
|
9
|
+
<a href="#"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue"/></a>
|
|
10
|
+
<a href="#"><img alt="status" src="https://img.shields.io/badge/status-beta-orange"/></a>
|
|
11
|
+
<a href="#"><img alt="dialects" src="https://img.shields.io/badge/dialects-mysql%20%7C%20postgresql%20%7C%20sqlite-6A5ACD"/></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Executive Summary
|
|
17
|
+
xansql is a minimalist but powerful ORM focusing on:
|
|
18
|
+
- Deterministic schema definition (single source of truth) with non-destructive migration.
|
|
19
|
+
- Relation traversal via declarative `select` trees (preventing circular graphs).
|
|
20
|
+
- Rich predicate language in `where` supporting deep `EXISTS` on nested relations.
|
|
21
|
+
- Event system & lifecycle hooks (global + per-model) for observability & cross-cutting concerns.
|
|
22
|
+
- Pluggable caching, file storage, fetch bridge (browser safe), and socket integration.
|
|
23
|
+
- Lightweight execution pipeline: thin SQL generation, no heavy runtime proxies.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
## Contents
|
|
27
|
+
1. Features
|
|
28
|
+
2. Architecture Overview
|
|
29
|
+
3. Installation
|
|
30
|
+
4. Quick Start
|
|
31
|
+
5. Configuration Reference
|
|
32
|
+
6. Defining Models & Fields
|
|
33
|
+
7. Relations
|
|
34
|
+
8. Querying & Predicates
|
|
35
|
+
9. Aggregation & Helpers
|
|
36
|
+
10. Pagination & Convenience APIs
|
|
37
|
+
11. Transactions
|
|
38
|
+
12. Migrations
|
|
39
|
+
13. Events & Hooks
|
|
40
|
+
14. File Handling
|
|
41
|
+
15. Client Fetch Bridge
|
|
42
|
+
16. Caching Interface
|
|
43
|
+
17. Dialects & Custom Implementation
|
|
44
|
+
18. Error Handling & Validation
|
|
45
|
+
19. Security Considerations
|
|
46
|
+
20. Performance Guidance
|
|
47
|
+
21. FAQ
|
|
48
|
+
22. Roadmap
|
|
49
|
+
23. License
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
## 1. Features
|
|
53
|
+
- Multi-dialect: MySQL, PostgreSQL, SQLite (custom adapter friendly)
|
|
54
|
+
- Auto aliasing + integrity checks
|
|
55
|
+
- Declarative relations (`xt.schema` / array reverse mapping)
|
|
56
|
+
- Non-destructive migrate (add/modify/remove columns) + force rebuild
|
|
57
|
+
- Granular lifecycle hooks & event emission
|
|
58
|
+
- Rich `where` condition operators (logical AND/OR composition)
|
|
59
|
+
- Nested relational filtering through `EXISTS` semantics
|
|
60
|
+
- Aggregation inline or via helper methods
|
|
61
|
+
- Optional caching module contract
|
|
62
|
+
- Integrated file meta handling & streaming upload abstraction
|
|
63
|
+
- Client-side safe execution (no raw SQL leakage) via signed execution meta
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
## 2. Architecture Overview
|
|
67
|
+
Layered components:
|
|
68
|
+
- Core: `Xansql` orchestrates config, model registry, transactions, migration, fetch bridge and events.
|
|
69
|
+
- Model: Provides CRUD + query generation + relation resolution.
|
|
70
|
+
- Executers: Specialized operation builders (Find / Create / Update / Delete / Aggregate).
|
|
71
|
+
- Migration: Computes delta from declared schema vs dialect metadata and issues SQL.
|
|
72
|
+
- Types System: Field factories (`xt.*`) with metadata (length, unique, index, validators, transforms).
|
|
73
|
+
- Foreign Resolver: Normalizes forward & reverse relation mapping for join/exists generation.
|
|
74
|
+
- Fetch Bridge: Validates request meta for client-originated operations (server controlled).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
## 3. Installation
|
|
78
|
+
```bash
|
|
79
|
+
npm install xansql mysql2 pg better-sqlite3
|
|
80
|
+
# Or only the drivers you need
|
|
81
|
+
```
|
|
82
|
+
SQLite usage recommends `better-sqlite3` for synchronous performance.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
## 4. Quick Start
|
|
86
|
+
```ts
|
|
87
|
+
import { Xansql, Model, xt } from 'xansql';
|
|
88
|
+
import MysqlDialect from 'xansql/dist/libs/MysqlDialect';
|
|
89
|
+
|
|
90
|
+
const db = new Xansql({
|
|
91
|
+
dialect: MysqlDialect({ host: '127.0.0.1', user: 'root', password: '', database: 'app' })
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const User = db.model('users', {
|
|
95
|
+
id: xt.id(),
|
|
96
|
+
username: xt.username(),
|
|
97
|
+
email: xt.email().unique(),
|
|
98
|
+
password: xt.password().strong(),
|
|
99
|
+
role: xt.role(['admin', 'member']),
|
|
100
|
+
createdAt: xt.createdAt(),
|
|
101
|
+
updatedAt: xt.updatedAt()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await db.migrate();
|
|
105
|
+
await User.create({ data: [{ username: 'alice', email: 'a@b.com', password: 'Pwd@1234', role: 'member' }] });
|
|
106
|
+
const result = await User.find({ where: { username: { equals: 'alice' } } });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
## 5. Configuration Reference
|
|
111
|
+
```ts
|
|
112
|
+
new Xansql({
|
|
113
|
+
dialect: MysqlDialect({...}), // REQUIRED
|
|
114
|
+
fetch: { url: '/xansql', mode: 'production' }, // optional (client bridge)
|
|
115
|
+
socket: { open, message, close }, // optional WebSocket handlers
|
|
116
|
+
cache: { cache, clear, onFind, onCreate, onUpdate, onDelete }, // optional
|
|
117
|
+
file: { maxFilesize, chunkSize, upload, delete }, // optional file storage
|
|
118
|
+
maxLimit: { find, create, update, delete }, // safety caps (default 100)
|
|
119
|
+
hooks: { beforeFind, afterFind, transform, ... } // global async hooks
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
Required dialect interface:
|
|
123
|
+
```ts
|
|
124
|
+
interface XansqlDialect {
|
|
125
|
+
engine: 'mysql' | 'postgresql' | 'sqlite';
|
|
126
|
+
execute(sql: string): Promise<{ results: any[]; affectedRows: number; insertId: number | null }>;
|
|
127
|
+
getSchema(): Promise<{ [table: string]: { name: string; type: string; notnull: boolean; default_value: any; pk: boolean; index: boolean; unique: boolean }[] }>
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
## 6. Defining Models & Fields
|
|
133
|
+
```ts
|
|
134
|
+
const Post = db.model('posts', {
|
|
135
|
+
id: xt.id(),
|
|
136
|
+
title: xt.title().index(),
|
|
137
|
+
slug: xt.slug().unique(),
|
|
138
|
+
author: xt.schema('users', 'id'), // FK forward
|
|
139
|
+
tags: xt.array(xt.string(30)), // array (not in where predicate)
|
|
140
|
+
images: xt.array(xt.file()), // file metadata entries
|
|
141
|
+
createdAt: xt.createdAt(),
|
|
142
|
+
updatedAt: xt.updatedAt()
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
Per-model hooks:
|
|
146
|
+
```ts
|
|
147
|
+
Post.options.hooks = {
|
|
148
|
+
beforeCreate: async (args) => args,
|
|
149
|
+
transform: async (row) => { delete row.password; return row; }
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
Field factory highlights: `id, string, number, boolean, date, enum, array, object, record, tuple, union, file, schema` + semantic shortcuts (`username`, `email`, `password`, `slug`, `role`, `title`, `amount`, etc.). Most fields accept chainable validators (`min`, `max`, `unique`, `index`, `transform`).
|
|
153
|
+
|
|
154
|
+
Foreign key patterns:
|
|
155
|
+
- Forward: `xt.schema('users','id')`
|
|
156
|
+
- Reverse (one-to-many): `xt.array(xt.schema('posts','id'))`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
## 7. Relations
|
|
160
|
+
Select nested relations:
|
|
161
|
+
```ts
|
|
162
|
+
await User.find({
|
|
163
|
+
select: {
|
|
164
|
+
id: true,
|
|
165
|
+
username: true,
|
|
166
|
+
posts: {
|
|
167
|
+
select: { id: true, title: true },
|
|
168
|
+
where: { title: { contains: 'SQL' } },
|
|
169
|
+
limit: { take: 5 }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
Circular graphs are rejected early.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
## 8. Querying & Predicates
|
|
178
|
+
Operators: `equals, not, lt, lte, gt, gte, in, notIn, between, notBetween, contains, notContains, startsWith, endsWith, isNull, isNotNull, isEmpty, isNotEmpty, isTrue, isFalse`.
|
|
179
|
+
- Object => AND
|
|
180
|
+
- Array of objects => OR
|
|
181
|
+
- Nested relation in `where` => EXISTS subquery
|
|
182
|
+
Example:
|
|
183
|
+
```ts
|
|
184
|
+
await Post.find({
|
|
185
|
+
where: {
|
|
186
|
+
author: { username: { startsWith: 'a' } },
|
|
187
|
+
slug: { notContains: 'draft' },
|
|
188
|
+
title: [{ contains: 'Guide' }, { contains: 'Intro' }]
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
## 9. Aggregation & Helpers
|
|
195
|
+
Inline:
|
|
196
|
+
```ts
|
|
197
|
+
await User.find({ aggregate: { id: { count: true } } });
|
|
198
|
+
```
|
|
199
|
+
Helpers: `count(where)`, `min(col, where)`, `max`, `sum`, `avg`, `exists(where)`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
## 10. Pagination & Convenience
|
|
203
|
+
```ts
|
|
204
|
+
const page = await User.paginate(2, { perpage: 20, where: { role: { equals: 'member' } } });
|
|
205
|
+
// { page, perpage, pagecount, rowcount, results }
|
|
206
|
+
```
|
|
207
|
+
Also: `findOne(args)`, `findById(id, args)`.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
## 11. Transactions
|
|
211
|
+
Automatic for create/update/delete unless within chained relation execution.
|
|
212
|
+
Manual wrapper:
|
|
213
|
+
```ts
|
|
214
|
+
await db.transaction(async () => {
|
|
215
|
+
await User.create({ data: [{ username: 'temp' }] });
|
|
216
|
+
await User.update({ data: { role: 'admin' }, where: { username: 'temp' } });
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
Rollback on error.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
## 12. Migrations
|
|
223
|
+
```ts
|
|
224
|
+
await db.migrate(); // sync non-destructively
|
|
225
|
+
await db.migrate(true); // drop + recreate (files cleaned)
|
|
226
|
+
const preview = await db.generateMigration(); // array of SQL statements
|
|
227
|
+
```
|
|
228
|
+
Rules:
|
|
229
|
+
- Skips ID column alterations.
|
|
230
|
+
- Adds new columns; drops removed ones; issues ALTER for changed definition.
|
|
231
|
+
- Force rebuild executes reverse-order drops then creates.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
## 13. Events & Hooks
|
|
235
|
+
Events emitted: `BEFORE_CREATE, CREATE, BEFORE_UPDATE, UPDATE, BEFORE_DELETE, DELETE, BEFORE_FIND, FIND, BEFORE_AGGREGATE, AGGREGATE, BEFORE_FETCH, FETCH`.
|
|
236
|
+
Usage:
|
|
237
|
+
```ts
|
|
238
|
+
db.on('CREATE', ({ model, results }) => { /* audit */ });
|
|
239
|
+
```
|
|
240
|
+
Hooks (global & model-level) allow mutation of args/results or row transform.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
## 14. File Handling
|
|
244
|
+
Define file fields: `xt.file(size?)` / arrays.
|
|
245
|
+
Configure storage:
|
|
246
|
+
```ts
|
|
247
|
+
file: {
|
|
248
|
+
maxFilesize: 2048, // KB
|
|
249
|
+
chunkSize: 256, // KB (streaming)
|
|
250
|
+
upload: async (chunk, meta) => {},
|
|
251
|
+
delete: async (filename) => {}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
Client helpers: `uploadFile(file, executeId)`, `deleteFile(name, executeId)`.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
## 15. Client Fetch Bridge
|
|
258
|
+
Provide `fetch: string | { url, mode }`.
|
|
259
|
+
Client side raw SQL blocked; operations require internally generated `executeId` (granted per model action via metadata).
|
|
260
|
+
Server integrates:
|
|
261
|
+
```ts
|
|
262
|
+
const response = await db.onFetch(req.url, {
|
|
263
|
+
body: req.body,
|
|
264
|
+
headers: req.headers,
|
|
265
|
+
cookies: parseCookies(req),
|
|
266
|
+
isAuthorized: async (meta) => {/* check meta.action, meta.model */ return true; }
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
## 16. Caching Interface
|
|
272
|
+
Implement partial or full row caching:
|
|
273
|
+
```ts
|
|
274
|
+
cache: {
|
|
275
|
+
cache: async (sql, model) => /* rows or undefined */,
|
|
276
|
+
clear: async (model) => {},
|
|
277
|
+
onFind: async (sql, model, row) => {},
|
|
278
|
+
onCreate: async (model, insertId) => {},
|
|
279
|
+
onUpdate: async (model, rows) => {},
|
|
280
|
+
onDelete: async (model, rows) => {},
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
You decide strategy (memory, redis, browser IndexedDB via example adapters).
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
## 17. Dialects & Custom Implementation
|
|
287
|
+
Built-ins: `MysqlDialect`, `PostgresDialect`, `SqliteDialect`.
|
|
288
|
+
Custom:
|
|
289
|
+
```ts
|
|
290
|
+
const CustomDialect = () => ({
|
|
291
|
+
engine: 'mysql',
|
|
292
|
+
execute: async (sql) => {/* run */ return { results: [], affectedRows: 0, insertId: 0 };},
|
|
293
|
+
getSchema: async () => ({ /* table: columns[] */ })
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
`getSchema` must supply column index/unique flags for migration diffing.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
## 18. Error Handling & Validation
|
|
300
|
+
Common thrown errors:
|
|
301
|
+
- Missing dialect or execute function
|
|
302
|
+
- Unsupported engine
|
|
303
|
+
- Model without ID field
|
|
304
|
+
- Duplicate model name / alias collision
|
|
305
|
+
- Invalid where operator or disallowed field type in predicate (array/object/record/tuple)
|
|
306
|
+
- Circular relation selection / where nesting
|
|
307
|
+
- Client usage without fetch configuration
|
|
308
|
+
- Raw query attempt from client without `executeId`
|
|
309
|
+
- Invalid foreign key definition
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
## 19. Security Considerations
|
|
313
|
+
- All value interpolation passes through escaping utilities.
|
|
314
|
+
- Client cannot send arbitrary SQL (requires signed meta created server-side).
|
|
315
|
+
- Hooks & events can enforce auditing, RBAC, masking.
|
|
316
|
+
- Password field helper automatically hashes via SHA-256 transform.
|
|
317
|
+
- Recommend additional app-layer input validation before invoking ORM.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
## 20. Performance Guidance
|
|
321
|
+
- Prefer selective `select` trees over full-table scans.
|
|
322
|
+
- Use indexes via field `.index()` / `.unique()` early (migration will create).
|
|
323
|
+
- Enable caching for heavy read patterns.
|
|
324
|
+
- Use pagination helpers (`paginate`) to avoid large offset scans.
|
|
325
|
+
- Keep relation depth shallow to limit EXISTS nesting.
|
|
326
|
+
- Batch `create` with array `data` for reduced round trips.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
## 21. FAQ
|
|
330
|
+
Q: Does xansql generate JOINs?
|
|
331
|
+
A: Relation filters use `EXISTS` subqueries; selection fetches related sets separately.
|
|
332
|
+
|
|
333
|
+
Q: How are reverse (one-to-many) relations defined?
|
|
334
|
+
A: `xt.array(xt.schema('childTable','id'))` inside the parent references children.
|
|
335
|
+
|
|
336
|
+
Q: Can I rename columns automatically?
|
|
337
|
+
A: Rename support is planned (see roadmap). Current diff treats rename as drop + add.
|
|
338
|
+
|
|
339
|
+
Q: Can I use raw SQL?
|
|
340
|
+
A: Server side `db.execute(sql)` is allowed; client side raw is blocked.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
## 22. Roadmap
|
|
344
|
+
- Column / index rename migration operations
|
|
345
|
+
- CLI code generation & schema inspector
|
|
346
|
+
- Enhanced diff reporting (explain changes)
|
|
347
|
+
- Advanced relation eager constraints (depth limiting strategies)
|
|
348
|
+
- Pluggable authorization middleware bundle
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
## 23. License
|
|
352
|
+
MIT
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
## Attributions
|
|
356
|
+
Internal field validation leverages concepts from `xanv`. File handling meta uses `securequ` upload structures.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
> Need adjustments (badges, examples, tutorials)? Open an issue or contribute.
|
package/utils/chunker.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';Object.defineProperty(exports,'__esModule',{value:true});/**
|
|
2
|
+
* Utility functions to chunk arrays or numbers into smaller parts
|
|
3
|
+
* @param length number of items
|
|
4
|
+
* @param perPage number of items per page
|
|
5
|
+
* @returns number of items per page
|
|
6
|
+
*/
|
|
7
|
+
const dynamicPerPage = (length, perPage) => {
|
|
8
|
+
if (perPage)
|
|
9
|
+
return perPage;
|
|
10
|
+
if (length <= 100)
|
|
11
|
+
return 50;
|
|
12
|
+
if (length <= 500)
|
|
13
|
+
return 250;
|
|
14
|
+
if (length <= 1000)
|
|
15
|
+
return 500;
|
|
16
|
+
if (length <= 3000)
|
|
17
|
+
return 1000;
|
|
18
|
+
if (length <= 5000)
|
|
19
|
+
return 1500;
|
|
20
|
+
if (length <= 10000)
|
|
21
|
+
return 2000;
|
|
22
|
+
return Math.min(Math.ceil(length / 10), 5000);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Generator: chunk an array into sub-arrays
|
|
26
|
+
* Example: chunkArray([1,2,3,4,5], 2) → [[1,2],[3,4],[5]]
|
|
27
|
+
*/
|
|
28
|
+
function* chunkArray(array, perPage) {
|
|
29
|
+
const length = array.length;
|
|
30
|
+
perPage = dynamicPerPage(length, perPage);
|
|
31
|
+
let chunkIndex = 0;
|
|
32
|
+
for (let i = 0; i < length; i += perPage) {
|
|
33
|
+
yield {
|
|
34
|
+
index: chunkIndex,
|
|
35
|
+
chunk: array.slice(i, i + perPage),
|
|
36
|
+
};
|
|
37
|
+
chunkIndex++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate batching steps as { take, skip }
|
|
42
|
+
* @param total Total items to process
|
|
43
|
+
* @param batchSize Size of each batch
|
|
44
|
+
* @param skipStart Optional starting skip value (default 0)
|
|
45
|
+
*/
|
|
46
|
+
function* chunkNumbers(total, skipStart = 0, perPage) {
|
|
47
|
+
perPage = dynamicPerPage(total, perPage);
|
|
48
|
+
for (let i = 0; i < total; i += perPage) {
|
|
49
|
+
const take = Math.min(perPage, total - i);
|
|
50
|
+
const skip = skipStart + i;
|
|
51
|
+
yield { take, skip };
|
|
52
|
+
}
|
|
53
|
+
}exports.chunkArray=chunkArray;exports.chunkNumbers=chunkNumbers;//# sourceMappingURL=chunker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.js","sources":["../../src/utils/chunker.ts"],"sourcesContent":["\n/**\n * Utility functions to chunk arrays or numbers into smaller parts\n * @param length number of items\n * @param perPage number of items per page\n * @returns number of items per page\n */\nconst dynamicPerPage = (length: number, perPage?: number) => {\n if (perPage) return perPage;\n if (length <= 100) return 50;\n if (length <= 500) return 250;\n if (length <= 1000) return 500;\n if (length <= 3000) return 1000;\n if (length <= 5000) return 1500;\n if (length <= 10000) return 2000;\n return Math.min(Math.ceil(length / 10), 5000);\n}\n\n/**\n * Generator: chunk an array into sub-arrays\n * Example: chunkArray([1,2,3,4,5], 2) → [[1,2],[3,4],[5]]\n */\nexport function* chunkArray<T = any>(array: T[], perPage?: number) {\n const length = array.length;\n perPage = dynamicPerPage(length, perPage);\n\n let chunkIndex = 0;\n for (let i = 0; i < length; i += perPage) {\n yield {\n index: chunkIndex,\n chunk: array.slice(i, i + perPage),\n };\n chunkIndex++;\n }\n}\n\n\n/**\n * Generate batching steps as { take, skip }\n * @param total Total items to process\n * @param batchSize Size of each batch\n * @param skipStart Optional starting skip value (default 0)\n */\nexport function* chunkNumbers(total: number, skipStart = 0, perPage?: number) {\n perPage = dynamicPerPage(total, perPage);\n for (let i = 0; i < total; i += perPage) {\n const take = Math.min(perPage, total - i);\n const skip = skipStart + i;\n yield { take, skip };\n }\n}\n"],"names":[],"mappings":"sEACA;;;;;AAKG;AACH,MAAM,cAAc,GAAG,CAAC,MAAc,EAAE,OAAgB,KAAI;AACzD,IAAA,IAAI,OAAO;AAAE,QAAA,OAAO,OAAO;IAC3B,IAAI,MAAM,IAAI,GAAG;AAAE,QAAA,OAAO,EAAE;IAC5B,IAAI,MAAM,IAAI,GAAG;AAAE,QAAA,OAAO,GAAG;IAC7B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,GAAG;IAC9B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,IAAI,MAAM,IAAI,KAAK;AAAE,QAAA,OAAO,IAAI;AAChC,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC;AAChD,CAAC;AAED;;;AAGG;UACc,UAAU,CAAU,KAAU,EAAE,OAAgB,EAAA;AAC9D,IAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;AAC3B,IAAA,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzC,IAAI,UAAU,GAAG,CAAC;AAClB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,OAAO,EAAE;QACvC,MAAM;AACH,YAAA,KAAK,EAAE,UAAU;YACjB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;SACpC;AACD,QAAA,UAAU,EAAE;IACf;AACH;AAGA;;;;;AAKG;AACG,UAAW,YAAY,CAAC,KAAa,EAAE,SAAS,GAAG,CAAC,EAAE,OAAgB,EAAA;AACzE,IAAA,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC;AACxC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,OAAO,EAAE;AACtC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC;AACzC,QAAA,MAAM,IAAI,GAAG,SAAS,GAAG,CAAC;AAC1B,QAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE;IACvB;AACH"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions to chunk arrays or numbers into smaller parts
|
|
3
|
+
* @param length number of items
|
|
4
|
+
* @param perPage number of items per page
|
|
5
|
+
* @returns number of items per page
|
|
6
|
+
*/
|
|
7
|
+
const dynamicPerPage = (length, perPage) => {
|
|
8
|
+
if (perPage)
|
|
9
|
+
return perPage;
|
|
10
|
+
if (length <= 100)
|
|
11
|
+
return 50;
|
|
12
|
+
if (length <= 500)
|
|
13
|
+
return 250;
|
|
14
|
+
if (length <= 1000)
|
|
15
|
+
return 500;
|
|
16
|
+
if (length <= 3000)
|
|
17
|
+
return 1000;
|
|
18
|
+
if (length <= 5000)
|
|
19
|
+
return 1500;
|
|
20
|
+
if (length <= 10000)
|
|
21
|
+
return 2000;
|
|
22
|
+
return Math.min(Math.ceil(length / 10), 5000);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Generator: chunk an array into sub-arrays
|
|
26
|
+
* Example: chunkArray([1,2,3,4,5], 2) → [[1,2],[3,4],[5]]
|
|
27
|
+
*/
|
|
28
|
+
function* chunkArray(array, perPage) {
|
|
29
|
+
const length = array.length;
|
|
30
|
+
perPage = dynamicPerPage(length, perPage);
|
|
31
|
+
let chunkIndex = 0;
|
|
32
|
+
for (let i = 0; i < length; i += perPage) {
|
|
33
|
+
yield {
|
|
34
|
+
index: chunkIndex,
|
|
35
|
+
chunk: array.slice(i, i + perPage),
|
|
36
|
+
};
|
|
37
|
+
chunkIndex++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate batching steps as { take, skip }
|
|
42
|
+
* @param total Total items to process
|
|
43
|
+
* @param batchSize Size of each batch
|
|
44
|
+
* @param skipStart Optional starting skip value (default 0)
|
|
45
|
+
*/
|
|
46
|
+
function* chunkNumbers(total, skipStart = 0, perPage) {
|
|
47
|
+
perPage = dynamicPerPage(total, perPage);
|
|
48
|
+
for (let i = 0; i < total; i += perPage) {
|
|
49
|
+
const take = Math.min(perPage, total - i);
|
|
50
|
+
const skip = skipStart + i;
|
|
51
|
+
yield { take, skip };
|
|
52
|
+
}
|
|
53
|
+
}export{chunkArray,chunkNumbers};//# sourceMappingURL=chunker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.mjs","sources":["../../src/utils/chunker.ts"],"sourcesContent":["\n/**\n * Utility functions to chunk arrays or numbers into smaller parts\n * @param length number of items\n * @param perPage number of items per page\n * @returns number of items per page\n */\nconst dynamicPerPage = (length: number, perPage?: number) => {\n if (perPage) return perPage;\n if (length <= 100) return 50;\n if (length <= 500) return 250;\n if (length <= 1000) return 500;\n if (length <= 3000) return 1000;\n if (length <= 5000) return 1500;\n if (length <= 10000) return 2000;\n return Math.min(Math.ceil(length / 10), 5000);\n}\n\n/**\n * Generator: chunk an array into sub-arrays\n * Example: chunkArray([1,2,3,4,5], 2) → [[1,2],[3,4],[5]]\n */\nexport function* chunkArray<T = any>(array: T[], perPage?: number) {\n const length = array.length;\n perPage = dynamicPerPage(length, perPage);\n\n let chunkIndex = 0;\n for (let i = 0; i < length; i += perPage) {\n yield {\n index: chunkIndex,\n chunk: array.slice(i, i + perPage),\n };\n chunkIndex++;\n }\n}\n\n\n/**\n * Generate batching steps as { take, skip }\n * @param total Total items to process\n * @param batchSize Size of each batch\n * @param skipStart Optional starting skip value (default 0)\n */\nexport function* chunkNumbers(total: number, skipStart = 0, perPage?: number) {\n perPage = dynamicPerPage(total, perPage);\n for (let i = 0; i < total; i += perPage) {\n const take = Math.min(perPage, total - i);\n const skip = skipStart + i;\n yield { take, skip };\n }\n}\n"],"names":[],"mappings":"AACA;;;;;AAKG;AACH,MAAM,cAAc,GAAG,CAAC,MAAc,EAAE,OAAgB,KAAI;AACzD,IAAA,IAAI,OAAO;AAAE,QAAA,OAAO,OAAO;IAC3B,IAAI,MAAM,IAAI,GAAG;AAAE,QAAA,OAAO,EAAE;IAC5B,IAAI,MAAM,IAAI,GAAG;AAAE,QAAA,OAAO,GAAG;IAC7B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,GAAG;IAC9B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,IAAI,MAAM,IAAI,IAAI;AAAE,QAAA,OAAO,IAAI;IAC/B,IAAI,MAAM,IAAI,KAAK;AAAE,QAAA,OAAO,IAAI;AAChC,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC;AAChD,CAAC;AAED;;;AAGG;UACc,UAAU,CAAU,KAAU,EAAE,OAAgB,EAAA;AAC9D,IAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;AAC3B,IAAA,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzC,IAAI,UAAU,GAAG,CAAC;AAClB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,OAAO,EAAE;QACvC,MAAM;AACH,YAAA,KAAK,EAAE,UAAU;YACjB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;SACpC;AACD,QAAA,UAAU,EAAE;IACf;AACH;AAGA;;;;;AAKG;AACG,UAAW,YAAY,CAAC,KAAa,EAAE,SAAS,GAAG,CAAC,EAAE,OAAgB,EAAA;AACzE,IAAA,OAAO,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC;AACxC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,OAAO,EAAE;AACtC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC;AACzC,QAAA,MAAM,IAAI,GAAG,SAAS,GAAG,CAAC;AAC1B,QAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE;IACvB;AACH"}
|
package/utils/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';Object.defineProperty(exports,'__esModule',{value:true});const isArray = (v) => Array.isArray(v);
|
|
2
|
+
// export const isObject = (v: any) => typeof v === 'object' && v !== null && !isArray(v) && !(v instanceof Date) && !(v instanceof RegExp) && !(v instanceof Buffer) && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer)
|
|
3
|
+
const isObject = (v) => Object.prototype.toString.call(v) === '[object Object]';
|
|
4
|
+
const isNumber = (v) => typeof v === 'number' && !isNaN(v);
|
|
5
|
+
const escapeSqlValue = (value) => {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/'/g, "''") // Escape single quote
|
|
8
|
+
.replace(/\x00/g, '\\0'); // Escape null byte (rare but can break SQL)
|
|
9
|
+
};
|
|
10
|
+
// export const ErrorWhene = (_if: any, message: string) => {
|
|
11
|
+
// if (_if) {
|
|
12
|
+
// throw new Error(message);
|
|
13
|
+
// }
|
|
14
|
+
// }
|
|
15
|
+
const quote = (engine, identifier) => {
|
|
16
|
+
if (engine === 'mysql')
|
|
17
|
+
return `\`${identifier}\``;
|
|
18
|
+
if (engine === 'postgresql' || engine === 'sqlite')
|
|
19
|
+
return `"${identifier}"`;
|
|
20
|
+
return identifier;
|
|
21
|
+
};
|
|
22
|
+
const uid = (str, length = 28) => {
|
|
23
|
+
let h1 = 0x811c9dc5;
|
|
24
|
+
let h2 = 0x811c9dc5 ^ str.length;
|
|
25
|
+
// Simple dual-hash loop
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
const c = str.charCodeAt(i);
|
|
28
|
+
h1 = Math.imul(h1 ^ c, 0x1000193);
|
|
29
|
+
h2 = Math.imul(h2 ^ (c + i * 17), 0x85ebca6b);
|
|
30
|
+
}
|
|
31
|
+
// Base36 mix gives letters + digits
|
|
32
|
+
let base = (h1 >>> 0).toString(36) + (h2 >>> 0).toString(36);
|
|
33
|
+
// Add derived chars from original string to strengthen variety
|
|
34
|
+
for (let i = 0; i < str.length; i++) {
|
|
35
|
+
const code = str.charCodeAt(i);
|
|
36
|
+
base += ((code * (i + 31)) % 36).toString(36);
|
|
37
|
+
}
|
|
38
|
+
// Scramble characters deterministically based on input
|
|
39
|
+
const arr = base.split('');
|
|
40
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
41
|
+
const j = (str.charCodeAt(i % str.length) + i * 19) % arr.length;
|
|
42
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
43
|
+
}
|
|
44
|
+
// Ensure letters are mixed in
|
|
45
|
+
const mixed = arr
|
|
46
|
+
.map((c, i) => i % 3 === 0 ? String.fromCharCode(97 + (c.charCodeAt(0) % 26)) : c)
|
|
47
|
+
.join('');
|
|
48
|
+
return mixed.slice(0, length);
|
|
49
|
+
};exports.escapeSqlValue=escapeSqlValue;exports.isArray=isArray;exports.isNumber=isNumber;exports.isObject=isObject;exports.quote=quote;exports.uid=uid;//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/utils/index.ts"],"sourcesContent":["import XanvType from \"xanv/XanvType\"\nimport { XansqlDialectEngine } from \"../core/type\";\n\n\nexport const isServer = () => typeof window === 'undefined'\nexport const isArray = (v: any) => Array.isArray(v)\n// export const isObject = (v: any) => typeof v === 'object' && v !== null && !isArray(v) && !(v instanceof Date) && !(v instanceof RegExp) && !(v instanceof Buffer) && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer)\nexport const isObject = (v: any) => Object.prototype.toString.call(v) === '[object Object]';\nexport const isString = (v: any) => typeof v === 'string'\nexport const isNumber = (v: any) => typeof v === 'number' && !isNaN(v)\nexport const isBoolean = (v: any) => typeof v === 'boolean'\n\nexport const formatValue = (v: any, xanv?: XanvType<any, any>): any => {\n if (isArray(v)) return v.map((item) => formatValue(item, xanv)).join(',')\n !!xanv && xanv.parse(v);\n if (v instanceof Date) v = v.toISOString()\n if (isString(v)) return `'${escapeSqlValue(v)}'`\n if (isNumber(v)) return v\n if (isBoolean(v)) return v ? 'TRUE' : 'FALSE'\n if (v === null) return 'NULL'\n if (v === undefined) return 'NULL'\n}\n\nexport const arrayMove = (arr: any[], fromIndex: number, toIndex: number) => {\n const newArr = [...arr];\n const item = newArr.splice(fromIndex, 1)[0];\n newArr.splice(toIndex, 0, item);\n return newArr;\n}\n\nexport const escapeSqlValue = (value: string): string => {\n return value\n .replace(/'/g, \"''\") // Escape single quote\n .replace(/\\x00/g, '\\\\0'); // Escape null byte (rare but can break SQL)\n}\n\n\nexport const freezeObject = (obj: any) => {\n Object.getOwnPropertyNames(obj).forEach((prop) => {\n const value = obj[prop];\n if (value && typeof value === \"object\") {\n freezeObject(value); // recursively freeze\n }\n });\n return Object.freeze(obj);\n}\n\n\n\n// export const ErrorWhene = (_if: any, message: string) => {\n// if (_if) {\n// throw new Error(message);\n// }\n// }\n\nexport const quote = (engine: XansqlDialectEngine, identifier: string) => {\n if (engine === 'mysql') return `\\`${identifier}\\``;\n if (engine === 'postgresql' || engine === 'sqlite') return `\"${identifier}\"`;\n return identifier;\n}\n\nexport const uid = (str: string, length = 28): string => {\n let h1 = 0x811c9dc5;\n let h2 = 0x811c9dc5 ^ str.length;\n\n // Simple dual-hash loop\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, 0x1000193);\n h2 = Math.imul(h2 ^ (c + i * 17), 0x85ebca6b);\n }\n\n // Base36 mix gives letters + digits\n let base = (h1 >>> 0).toString(36) + (h2 >>> 0).toString(36);\n\n // Add derived chars from original string to strengthen variety\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n base += ((code * (i + 31)) % 36).toString(36);\n }\n\n // Scramble characters deterministically based on input\n const arr = base.split('');\n for (let i = arr.length - 1; i > 0; i--) {\n const j = (str.charCodeAt(i % str.length) + i * 19) % arr.length;\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n\n // Ensure letters are mixed in\n const mixed = arr\n .map((c, i) =>\n i % 3 === 0 ? String.fromCharCode(97 + (c.charCodeAt(0) % 26)) : c\n )\n .join('');\n\n return mixed.slice(0, length);\n}\n\nexport function hash(length = 16): string {\n let result = '';\n while (result.length < length) {\n result += Math.random().toString(36).slice(2);\n }\n return result.slice(0, length);\n}\n\n"],"names":[],"mappings":"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"}
|
package/utils/index.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const isArray = (v) => Array.isArray(v);
|
|
2
|
+
// export const isObject = (v: any) => typeof v === 'object' && v !== null && !isArray(v) && !(v instanceof Date) && !(v instanceof RegExp) && !(v instanceof Buffer) && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer)
|
|
3
|
+
const isObject = (v) => Object.prototype.toString.call(v) === '[object Object]';
|
|
4
|
+
const isNumber = (v) => typeof v === 'number' && !isNaN(v);
|
|
5
|
+
const escapeSqlValue = (value) => {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/'/g, "''") // Escape single quote
|
|
8
|
+
.replace(/\x00/g, '\\0'); // Escape null byte (rare but can break SQL)
|
|
9
|
+
};
|
|
10
|
+
// export const ErrorWhene = (_if: any, message: string) => {
|
|
11
|
+
// if (_if) {
|
|
12
|
+
// throw new Error(message);
|
|
13
|
+
// }
|
|
14
|
+
// }
|
|
15
|
+
const quote = (engine, identifier) => {
|
|
16
|
+
if (engine === 'mysql')
|
|
17
|
+
return `\`${identifier}\``;
|
|
18
|
+
if (engine === 'postgresql' || engine === 'sqlite')
|
|
19
|
+
return `"${identifier}"`;
|
|
20
|
+
return identifier;
|
|
21
|
+
};
|
|
22
|
+
const uid = (str, length = 28) => {
|
|
23
|
+
let h1 = 0x811c9dc5;
|
|
24
|
+
let h2 = 0x811c9dc5 ^ str.length;
|
|
25
|
+
// Simple dual-hash loop
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
const c = str.charCodeAt(i);
|
|
28
|
+
h1 = Math.imul(h1 ^ c, 0x1000193);
|
|
29
|
+
h2 = Math.imul(h2 ^ (c + i * 17), 0x85ebca6b);
|
|
30
|
+
}
|
|
31
|
+
// Base36 mix gives letters + digits
|
|
32
|
+
let base = (h1 >>> 0).toString(36) + (h2 >>> 0).toString(36);
|
|
33
|
+
// Add derived chars from original string to strengthen variety
|
|
34
|
+
for (let i = 0; i < str.length; i++) {
|
|
35
|
+
const code = str.charCodeAt(i);
|
|
36
|
+
base += ((code * (i + 31)) % 36).toString(36);
|
|
37
|
+
}
|
|
38
|
+
// Scramble characters deterministically based on input
|
|
39
|
+
const arr = base.split('');
|
|
40
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
41
|
+
const j = (str.charCodeAt(i % str.length) + i * 19) % arr.length;
|
|
42
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
43
|
+
}
|
|
44
|
+
// Ensure letters are mixed in
|
|
45
|
+
const mixed = arr
|
|
46
|
+
.map((c, i) => i % 3 === 0 ? String.fromCharCode(97 + (c.charCodeAt(0) % 26)) : c)
|
|
47
|
+
.join('');
|
|
48
|
+
return mixed.slice(0, length);
|
|
49
|
+
};export{escapeSqlValue,isArray,isNumber,isObject,quote,uid};//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/utils/index.ts"],"sourcesContent":["import XanvType from \"xanv/XanvType\"\nimport { XansqlDialectEngine } from \"../core/type\";\n\n\nexport const isServer = () => typeof window === 'undefined'\nexport const isArray = (v: any) => Array.isArray(v)\n// export const isObject = (v: any) => typeof v === 'object' && v !== null && !isArray(v) && !(v instanceof Date) && !(v instanceof RegExp) && !(v instanceof Buffer) && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer)\nexport const isObject = (v: any) => Object.prototype.toString.call(v) === '[object Object]';\nexport const isString = (v: any) => typeof v === 'string'\nexport const isNumber = (v: any) => typeof v === 'number' && !isNaN(v)\nexport const isBoolean = (v: any) => typeof v === 'boolean'\n\nexport const formatValue = (v: any, xanv?: XanvType<any, any>): any => {\n if (isArray(v)) return v.map((item) => formatValue(item, xanv)).join(',')\n !!xanv && xanv.parse(v);\n if (v instanceof Date) v = v.toISOString()\n if (isString(v)) return `'${escapeSqlValue(v)}'`\n if (isNumber(v)) return v\n if (isBoolean(v)) return v ? 'TRUE' : 'FALSE'\n if (v === null) return 'NULL'\n if (v === undefined) return 'NULL'\n}\n\nexport const arrayMove = (arr: any[], fromIndex: number, toIndex: number) => {\n const newArr = [...arr];\n const item = newArr.splice(fromIndex, 1)[0];\n newArr.splice(toIndex, 0, item);\n return newArr;\n}\n\nexport const escapeSqlValue = (value: string): string => {\n return value\n .replace(/'/g, \"''\") // Escape single quote\n .replace(/\\x00/g, '\\\\0'); // Escape null byte (rare but can break SQL)\n}\n\n\nexport const freezeObject = (obj: any) => {\n Object.getOwnPropertyNames(obj).forEach((prop) => {\n const value = obj[prop];\n if (value && typeof value === \"object\") {\n freezeObject(value); // recursively freeze\n }\n });\n return Object.freeze(obj);\n}\n\n\n\n// export const ErrorWhene = (_if: any, message: string) => {\n// if (_if) {\n// throw new Error(message);\n// }\n// }\n\nexport const quote = (engine: XansqlDialectEngine, identifier: string) => {\n if (engine === 'mysql') return `\\`${identifier}\\``;\n if (engine === 'postgresql' || engine === 'sqlite') return `\"${identifier}\"`;\n return identifier;\n}\n\nexport const uid = (str: string, length = 28): string => {\n let h1 = 0x811c9dc5;\n let h2 = 0x811c9dc5 ^ str.length;\n\n // Simple dual-hash loop\n for (let i = 0; i < str.length; i++) {\n const c = str.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, 0x1000193);\n h2 = Math.imul(h2 ^ (c + i * 17), 0x85ebca6b);\n }\n\n // Base36 mix gives letters + digits\n let base = (h1 >>> 0).toString(36) + (h2 >>> 0).toString(36);\n\n // Add derived chars from original string to strengthen variety\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n base += ((code * (i + 31)) % 36).toString(36);\n }\n\n // Scramble characters deterministically based on input\n const arr = base.split('');\n for (let i = arr.length - 1; i > 0; i--) {\n const j = (str.charCodeAt(i % str.length) + i * 19) % arr.length;\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n\n // Ensure letters are mixed in\n const mixed = arr\n .map((c, i) =>\n i % 3 === 0 ? String.fromCharCode(97 + (c.charCodeAt(0) % 26)) : c\n )\n .join('');\n\n return mixed.slice(0, length);\n}\n\nexport function hash(length = 16): string {\n let result = '';\n while (result.length < length) {\n result += Math.random().toString(36).slice(2);\n }\n return result.slice(0, length);\n}\n\n"],"names":[],"mappings":"AAKO,MAAM,OAAO,GAAG,CAAC,CAAM,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;AAClD;MACa,QAAQ,GAAG,CAAC,CAAM,KAAK,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;AAEnE,MAAM,QAAQ,GAAG,CAAC,CAAM,KAAK,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC;AAqB9D,MAAM,cAAc,GAAG,CAAC,KAAa,KAAY;AACrD,IAAA,OAAO;AACH,SAAA,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;AACnB,SAAA,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC/B;AAeA;AACA;AACA;AACA;AACA;MAEa,KAAK,GAAG,CAAC,MAA2B,EAAE,UAAkB,KAAI;IACtE,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,CAAA,EAAA,EAAK,UAAU,CAAA,EAAA,CAAI;AAClD,IAAA,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,CAAG;AAC5E,IAAA,OAAO,UAAU;AACpB;AAEO,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,MAAM,GAAG,EAAE,KAAY;IACrD,IAAI,EAAE,GAAG,UAAU;AACnB,IAAA,IAAI,EAAE,GAAG,UAAU,GAAG,GAAG,CAAC,MAAM;;AAGhC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3B,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC;AACjC,QAAA,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,UAAU,CAAC;IAChD;;IAGA,IAAI,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;;AAG5D,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9B,QAAA,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC;IAChD;;IAGA,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AAC1B,IAAA,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;QACtC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,MAAM;QAChE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACtC;;IAGA,MAAM,KAAK,GAAG;AACV,SAAA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KACP,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC;SAEpE,IAAI,CAAC,EAAE,CAAC;IAEZ,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;AAChC"}
|