@datrix/adapter-json 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 datrix Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # datrix-adapter-json
2
+
3
+ A file-based JSON database adapter for Datrix framework. Perfect for development, testing, static sites, and small-scale applications.
4
+
5
+ ## Features
6
+
7
+ - **File-based storage** - Each table stored as a single JSON file
8
+ - **Full CRUD operations** - Create, read, update, delete with type safety
9
+ - **Relations support** - belongsTo, hasMany, hasOne, manyToMany with eager loading
10
+ - **Query features** - WHERE clauses, SELECT projection, ORDER BY, LIMIT, OFFSET, DISTINCT
11
+ - **Schema-based validation** - Automatic validation using Datrix schemas
12
+ - **Migration support** - Create, alter tables, add indexes
13
+ - **Cache mechanism** - mtime-based caching for performance
14
+ - **Thread-safe** - File-level locking for concurrent operations
15
+ - **Zero dependencies** - Only requires Node.js fs module
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ # pnpm
21
+ pnpm add datrix-adapter-json
22
+
23
+ # yarn
24
+ yarn add datrix-adapter-json
25
+
26
+ # npm
27
+ npm install datrix-adapter-json
28
+ ```
29
+
30
+ ## Quick Start with Datrix
31
+
32
+ ```typescript
33
+ import { defineConfig } from "datrix-core";
34
+ import { JsonAdapter } from "datrix-adapter-json";
35
+
36
+ // Create Datrix configuration
37
+ const config = defineConfig(() => ({
38
+ adapter: new JsonAdapter({
39
+ root: "./data",
40
+ }),
41
+
42
+ schemas: [
43
+ // Your schemas here (see Datrix documentation)
44
+ ],
45
+
46
+ plugins: [
47
+ // Your plugins here
48
+ ],
49
+ }));
50
+ ```
51
+
52
+ ## Configuration Options
53
+
54
+ ### `JsonAdapterConfig`
55
+
56
+ ```typescript
57
+ interface JsonAdapterConfig {
58
+ /**
59
+ * Root directory for JSON files (required)
60
+ * Each table will be stored as {root}/{tableName}.json
61
+ */
62
+ root: string;
63
+
64
+ /**
65
+ * Lock acquisition timeout in milliseconds (optional)
66
+ * Default: 5000ms (5 seconds)
67
+ *
68
+ * When a write operation is attempted, the adapter will wait this long
69
+ * for the lock to be acquired before throwing an error.
70
+ */
71
+ lockTimeout?: number;
72
+
73
+ /**
74
+ * Stale lock timeout in milliseconds (optional)
75
+ * Default: 30000ms (30 seconds)
76
+ *
77
+ * If a lock file is older than this duration, it's considered stale
78
+ * and will be automatically removed. Prevents deadlocks from crashed processes.
79
+ */
80
+ staleTimeout?: number;
81
+
82
+ /**
83
+ * Enable in-memory cache (optional)
84
+ * Default: true
85
+ *
86
+ * Caches parsed JSON data in memory with mtime validation.
87
+ * Cache is invalidated when file modification time changes.
88
+ * Significantly improves read performance for repeated queries.
89
+ */
90
+ cache?: boolean;
91
+
92
+ /**
93
+ * Require lock for read operations (optional)
94
+ * Default: false
95
+ *
96
+ * Enable if you need strict read consistency in scenarios with
97
+ * concurrent writes. Adds overhead but ensures reads don't happen
98
+ * during writes.
99
+ */
100
+ readLock?: boolean;
101
+ }
102
+ ```
103
+
104
+ ### Example
105
+
106
+ ```typescript
107
+ const adapter = new JsonAdapter({
108
+ root: "./database",
109
+ lockTimeout: 10000, // Wait 10s for locks
110
+ staleTimeout: 60000, // Consider locks stale after 1min
111
+ cache: true, // Enable caching (default)
112
+ readLock: false, // Don't lock on reads (default)
113
+ });
114
+ ```
115
+
116
+ ## Use Cases
117
+
118
+ ### ✅ Good For
119
+
120
+ - **Development & Testing** - Fast setup, easy inspection, no external dependencies
121
+ - **Static Site Generation** - Build-time data fetching, no runtime database needed
122
+ - **Prototyping** - Quick POC without database setup
123
+ - **Small Applications** - <10k records per table, low concurrent writes
124
+ - **Content Management** - Blog posts, static content with relations
125
+ - **Dev Environment** - Local development with production-like data structure
126
+
127
+ ### ❌ Not Suitable For
128
+
129
+ - **Production Applications** - High traffic, concurrent writes
130
+ - **Large Datasets** - >10k records per table (performance degrades)
131
+ - **Real-time Applications** - File I/O overhead too high
132
+ - **High Concurrency** - File-level locking limits throughput
133
+ - **Transactional Workloads** - Limited transaction support
134
+
135
+ ## Performance Characteristics
136
+
137
+ - **Read Operations** - Fast with caching (~1-5ms for cached, ~10-50ms for uncached)
138
+ - **Write Operations** - Slower due to full file rewrite (~50-200ms depending on file size)
139
+ - **Populate Operations** - Efficient with cache, multiple file reads needed
140
+ - **Concurrent Writes** - Serialized with file locking, not suitable for high concurrency
141
+ - **Memory Usage** - Entire table loaded in memory during operations
142
+ - **File Size Impact** - Linear degradation (1k records ≈ 100KB, 10k records ≈ 1MB)
143
+
144
+ ## File Structure
145
+
146
+ ```
147
+ data/
148
+ ├── users.json # User table
149
+ ├── posts.json # Post table
150
+ ├── categories.json # Category table
151
+ └── post_categories.json # Junction table (manyToMany)
152
+ ```
153
+
154
+ Each file contains:
155
+
156
+ ```json
157
+ {
158
+ "meta": {
159
+ "version": 1,
160
+ "name": "users",
161
+ "lastInsertId": 3,
162
+ "updatedAt": "2026-01-27T10:30:00.000Z"
163
+ },
164
+ "data": [
165
+ { "id": 1, "name": "Alice", "email": "alice@example.com" },
166
+ { "id": 2, "name": "Bob", "email": "bob@example.com" }
167
+ ]
168
+ }
169
+ ```
170
+
171
+ ## Standalone Usage
172
+
173
+ For using JsonAdapter without Datrix framework, see the comprehensive guide:
174
+
175
+ Check **[HOW_TO_USE.md](./HOW_TO_USE.md)**
176
+
177
+ Covers:
178
+
179
+ - Manual setup and schema definition
180
+ - Direct CRUD operations
181
+ - Complex queries and filters
182
+ - Best practices and naming conventions
183
+
184
+ ## Relations & Populate
185
+
186
+ JsonAdapter supports all Datrix relation types:
187
+
188
+ | Type | Description | FK Location | Result |
189
+ | -------------- | ------------ | -------------- | --------------------- |
190
+ | **belongsTo** | Many-to-one | Source table | Single object or null |
191
+ | **hasMany** | One-to-many | Target table | Array (can be empty) |
192
+ | **hasOne** | One-to-one | Target table | Single object or null |
193
+ | **manyToMany** | Many-to-many | Junction table | Array (can be empty) |
194
+
195
+ ### Populate Syntax
196
+
197
+ ```typescript
198
+ // Simple - all fields
199
+ populate: { author: "*" }
200
+
201
+ // With field selection
202
+ populate: {
203
+ author: {
204
+ select: ["name", "email"]
205
+ }
206
+ }
207
+
208
+ // Nested populate
209
+ populate: {
210
+ author: {
211
+ populate: {
212
+ profile: "*"
213
+ }
214
+ }
215
+ }
216
+
217
+ // Multiple relations
218
+ populate: {
219
+ author: "*",
220
+ comments: { select: ["text"] },
221
+ tags: "*"
222
+ }
223
+ ```
224
+
225
+ **Note:** Populate is handled by Datrix core. See Datrix documentation for query API details.
226
+
227
+ ## Error Handling
228
+
229
+ Operations throw `DatrixAdapterError` on failure:
230
+
231
+ ```typescript
232
+ import { DatrixAdapterError } from "@datrix/core";
233
+
234
+ try {
235
+ const result = await adapter.executeQuery({
236
+ type: "select",
237
+ table: "users",
238
+ });
239
+ console.log(result.rows);
240
+ } catch (error) {
241
+ if (error instanceof DatrixAdapterError) {
242
+ console.error(error.code); // "ADAPTER_QUERY_ERROR"
243
+ console.error(error.message); // Detailed message
244
+ }
245
+ }
246
+ ```
247
+
248
+ ## License
249
+
250
+ MIT © Datrix Contributors
251
+
252
+ ## Links
253
+
254
+ - [Datrix Documentation](https://datrix.dev) - Configuration, schemas, migrations
255
+ - [How to Use Guide](./HOW_TO_USE.md) - Standalone usage without Datrix
256
+ - [GitHub Repository](https://github.com/myniqx/datrix)
@@ -0,0 +1,88 @@
1
+ import { DatabaseAdapter, ConnectionState, SchemaDefinition, ExportWriter, ImportReader, DatrixEntry, QueryObject, QueryResult, Transaction, AlterOperation, IndexDefinition } from '@datrix/core';
2
+
3
+ interface JsonAdapterConfig extends Record<string, unknown> {
4
+ readonly root: string;
5
+ lockTimeout?: number;
6
+ staleTimeout?: number;
7
+ cache?: boolean;
8
+ readLock?: boolean;
9
+ standalone?: boolean;
10
+ }
11
+ interface JsonTableFile<T = Record<string, unknown>> {
12
+ meta: {
13
+ version: number;
14
+ lastInsertId?: number;
15
+ updatedAt: string;
16
+ name: string;
17
+ };
18
+ data: T[];
19
+ }
20
+ interface CacheEntry {
21
+ data: JsonTableFile;
22
+ mtime: number;
23
+ }
24
+ interface ExecuteQueryOptions {
25
+ skipLock?: boolean;
26
+ skipWrite?: boolean;
27
+ }
28
+ interface SchemaOperationOptions {
29
+ skipLock?: boolean;
30
+ skipWrite?: boolean;
31
+ isImport?: boolean;
32
+ }
33
+
34
+ declare class JsonAdapter implements DatabaseAdapter<JsonAdapterConfig> {
35
+ readonly name = "json";
36
+ readonly config: JsonAdapterConfig;
37
+ private state;
38
+ private cache;
39
+ private lock;
40
+ private cacheEnabled;
41
+ private readLockEnabled;
42
+ private activeTransactionCache;
43
+ private activeTransactionModifiedTables;
44
+ private activeTransactionDeletedTables;
45
+ constructor(config: JsonAdapterConfig);
46
+ connect(): Promise<void>;
47
+ disconnect(): Promise<void>;
48
+ isConnected(): boolean;
49
+ getConnectionState(): ConnectionState;
50
+ private getTablePath;
51
+ private readTable;
52
+ getCachedTable(tableName: string): Promise<JsonTableFile | null>;
53
+ getSchemaByTableName(tableName: string): Promise<SchemaDefinition | null>;
54
+ getSchemaByModelName(modelName: string): Promise<SchemaDefinition | null>;
55
+ findTableNameByModelName(modelName: string): Promise<string | null>;
56
+ readTableSchema(tableName: string): Promise<SchemaDefinition>;
57
+ private upsertSchemaMeta;
58
+ private applyOperationsToMetaSchema;
59
+ private invalidateCache;
60
+ private updateCache;
61
+ exportData(writer: ExportWriter): Promise<void>;
62
+ importData(reader: ImportReader): Promise<void>;
63
+ createTable(schema: SchemaDefinition, options?: {
64
+ isImport?: boolean;
65
+ }): Promise<void>;
66
+ createTableWithOptions(schema: SchemaDefinition, options?: SchemaOperationOptions, isImport?: boolean): Promise<void>;
67
+ dropTable(tableName: string): Promise<void>;
68
+ dropTableWithOptions(tableName: string, options?: SchemaOperationOptions): Promise<void>;
69
+ executeQuery<TResult extends DatrixEntry>(query: QueryObject<TResult>): Promise<QueryResult<TResult>>;
70
+ executeQueryWithOptions<TResult extends DatrixEntry>(query: QueryObject<TResult>, options?: ExecuteQueryOptions): Promise<QueryResult<TResult>>;
71
+ executeRawQuery<TResult extends DatrixEntry>(_sql: string, _params: readonly unknown[]): Promise<QueryResult<TResult>>;
72
+ beginTransaction(): Promise<Transaction>;
73
+ private commitTransaction;
74
+ private rollbackTransaction;
75
+ alterTable(tableName: string, operations: readonly AlterOperation[]): Promise<void>;
76
+ alterTableWithOptions(tableName: string, operations: readonly AlterOperation[], options?: SchemaOperationOptions): Promise<void>;
77
+ renameTable(from: string, to: string): Promise<void>;
78
+ renameTableWithOptions(from: string, to: string, options?: SchemaOperationOptions): Promise<void>;
79
+ addIndex(tableName: string, index: IndexDefinition): Promise<void>;
80
+ addIndexWithOptions(_tableName: string, _index: IndexDefinition, _options?: SchemaOperationOptions): Promise<void>;
81
+ dropIndex(tableName: string, indexName: string): Promise<void>;
82
+ dropIndexWithOptions(_tableName: string, _indexName: string, _options?: SchemaOperationOptions): Promise<void>;
83
+ getTables(): Promise<readonly string[]>;
84
+ getTableSchema(tableName: string): Promise<SchemaDefinition | null>;
85
+ tableExists(tableName: string): Promise<boolean>;
86
+ }
87
+
88
+ export { type CacheEntry, type ExecuteQueryOptions, JsonAdapter, type JsonAdapterConfig, type JsonTableFile, type SchemaOperationOptions };
@@ -0,0 +1,88 @@
1
+ import { DatabaseAdapter, ConnectionState, SchemaDefinition, ExportWriter, ImportReader, DatrixEntry, QueryObject, QueryResult, Transaction, AlterOperation, IndexDefinition } from '@datrix/core';
2
+
3
+ interface JsonAdapterConfig extends Record<string, unknown> {
4
+ readonly root: string;
5
+ lockTimeout?: number;
6
+ staleTimeout?: number;
7
+ cache?: boolean;
8
+ readLock?: boolean;
9
+ standalone?: boolean;
10
+ }
11
+ interface JsonTableFile<T = Record<string, unknown>> {
12
+ meta: {
13
+ version: number;
14
+ lastInsertId?: number;
15
+ updatedAt: string;
16
+ name: string;
17
+ };
18
+ data: T[];
19
+ }
20
+ interface CacheEntry {
21
+ data: JsonTableFile;
22
+ mtime: number;
23
+ }
24
+ interface ExecuteQueryOptions {
25
+ skipLock?: boolean;
26
+ skipWrite?: boolean;
27
+ }
28
+ interface SchemaOperationOptions {
29
+ skipLock?: boolean;
30
+ skipWrite?: boolean;
31
+ isImport?: boolean;
32
+ }
33
+
34
+ declare class JsonAdapter implements DatabaseAdapter<JsonAdapterConfig> {
35
+ readonly name = "json";
36
+ readonly config: JsonAdapterConfig;
37
+ private state;
38
+ private cache;
39
+ private lock;
40
+ private cacheEnabled;
41
+ private readLockEnabled;
42
+ private activeTransactionCache;
43
+ private activeTransactionModifiedTables;
44
+ private activeTransactionDeletedTables;
45
+ constructor(config: JsonAdapterConfig);
46
+ connect(): Promise<void>;
47
+ disconnect(): Promise<void>;
48
+ isConnected(): boolean;
49
+ getConnectionState(): ConnectionState;
50
+ private getTablePath;
51
+ private readTable;
52
+ getCachedTable(tableName: string): Promise<JsonTableFile | null>;
53
+ getSchemaByTableName(tableName: string): Promise<SchemaDefinition | null>;
54
+ getSchemaByModelName(modelName: string): Promise<SchemaDefinition | null>;
55
+ findTableNameByModelName(modelName: string): Promise<string | null>;
56
+ readTableSchema(tableName: string): Promise<SchemaDefinition>;
57
+ private upsertSchemaMeta;
58
+ private applyOperationsToMetaSchema;
59
+ private invalidateCache;
60
+ private updateCache;
61
+ exportData(writer: ExportWriter): Promise<void>;
62
+ importData(reader: ImportReader): Promise<void>;
63
+ createTable(schema: SchemaDefinition, options?: {
64
+ isImport?: boolean;
65
+ }): Promise<void>;
66
+ createTableWithOptions(schema: SchemaDefinition, options?: SchemaOperationOptions, isImport?: boolean): Promise<void>;
67
+ dropTable(tableName: string): Promise<void>;
68
+ dropTableWithOptions(tableName: string, options?: SchemaOperationOptions): Promise<void>;
69
+ executeQuery<TResult extends DatrixEntry>(query: QueryObject<TResult>): Promise<QueryResult<TResult>>;
70
+ executeQueryWithOptions<TResult extends DatrixEntry>(query: QueryObject<TResult>, options?: ExecuteQueryOptions): Promise<QueryResult<TResult>>;
71
+ executeRawQuery<TResult extends DatrixEntry>(_sql: string, _params: readonly unknown[]): Promise<QueryResult<TResult>>;
72
+ beginTransaction(): Promise<Transaction>;
73
+ private commitTransaction;
74
+ private rollbackTransaction;
75
+ alterTable(tableName: string, operations: readonly AlterOperation[]): Promise<void>;
76
+ alterTableWithOptions(tableName: string, operations: readonly AlterOperation[], options?: SchemaOperationOptions): Promise<void>;
77
+ renameTable(from: string, to: string): Promise<void>;
78
+ renameTableWithOptions(from: string, to: string, options?: SchemaOperationOptions): Promise<void>;
79
+ addIndex(tableName: string, index: IndexDefinition): Promise<void>;
80
+ addIndexWithOptions(_tableName: string, _index: IndexDefinition, _options?: SchemaOperationOptions): Promise<void>;
81
+ dropIndex(tableName: string, indexName: string): Promise<void>;
82
+ dropIndexWithOptions(_tableName: string, _indexName: string, _options?: SchemaOperationOptions): Promise<void>;
83
+ getTables(): Promise<readonly string[]>;
84
+ getTableSchema(tableName: string): Promise<SchemaDefinition | null>;
85
+ tableExists(tableName: string): Promise<boolean>;
86
+ }
87
+
88
+ export { type CacheEntry, type ExecuteQueryOptions, JsonAdapter, type JsonAdapterConfig, type JsonTableFile, type SchemaOperationOptions };