@asaidimu/anansi 1.6.6 → 2.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/README.md +750 -65
- package/index.cjs +13 -13
- package/index.d.cts +733 -164
- package/index.d.ts +733 -164
- package/index.js +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Anansi Schema Evolution Platform
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@asaidimu/anansi)
|
|
4
|
+
[](LICENSE.md)
|
|
5
|
+
[](https://github.com/asaidimu/data-model/actions)
|
|
6
|
+
|
|
7
|
+
A comprehensive toolkit for advanced data modelling, schema evolution, and adaptive persistence management in complex enterprise systems.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Why the Name "Anansi"?](#why-the-name-anansi)
|
|
14
|
+
- [Theoretical Foundation](#theoretical-foundation)
|
|
15
|
+
- [Overview & Features](#overview--features)
|
|
16
|
+
- [Detailed Description](#detailed-description)
|
|
17
|
+
- [Key Features](#key-features)
|
|
18
|
+
- [Installation & Setup](#installation--setup)
|
|
19
|
+
- [Prerequisites](#prerequisites)
|
|
20
|
+
- [Installation Steps](#installation-steps)
|
|
21
|
+
- [Configuration](#configuration)
|
|
22
|
+
- [Usage Documentation](#usage-documentation)
|
|
23
|
+
- [Defining a Schema](#defining-a-schema)
|
|
24
|
+
- [Ephemeral Persistence](#ephemeral-persistence)
|
|
25
|
+
- [Schema Registry (Git-backed)](#schema-registry-git-backed)
|
|
26
|
+
- [Schema Migration](#schema-migration)
|
|
27
|
+
- [Type Generation](#type-generation)
|
|
28
|
+
- [Documentation Generation](#documentation-generation)
|
|
29
|
+
- [Project Architecture](#project-architecture)
|
|
30
|
+
- [Directory Structure](#directory-structure)
|
|
31
|
+
- [Core Components](#core-components)
|
|
32
|
+
- [Data Flow](#data-flow)
|
|
33
|
+
- [Extension Points](#extension-points)
|
|
34
|
+
- [Development & Contributing](#development--contributing)
|
|
35
|
+
- [Development Setup](#development-setup)
|
|
36
|
+
- [Available Scripts](#available-scripts)
|
|
37
|
+
- [Testing](#testing)
|
|
38
|
+
- [Contributing Guidelines](#contributing-guidelines)
|
|
39
|
+
- [Issue Reporting](#issue-reporting)
|
|
40
|
+
- [Additional Information](#additional-information)
|
|
41
|
+
- [Troubleshooting](#troubleshooting)
|
|
42
|
+
- [FAQ](#faq)
|
|
43
|
+
- [Changelog](#changelog)
|
|
44
|
+
- [License](#license)
|
|
45
|
+
- [Acknowledgments](#acknowledgments)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
3
49
|
## Why the Name "Anansi"?
|
|
4
50
|
|
|
5
51
|
Named after the legendary Akan trickster god of West Africa, our platform embodies Anansi's core attributes:
|
|
@@ -13,92 +59,731 @@ The name reflects our philosophical approach: transformative, intelligent, and d
|
|
|
13
59
|
|
|
14
60
|
A mathematically rigorous framework for managing data model complexity, grounded in advanced theoretical principles of system evolution and distributed computing.
|
|
15
61
|
|
|
16
|
-
## Overview
|
|
62
|
+
## Overview & Features
|
|
63
|
+
|
|
64
|
+
### Detailed Description
|
|
65
|
+
|
|
66
|
+
Anansi is a comprehensive platform built to tackle the challenges of modern enterprise data management. It provides a principled approach to schema design, evolution, and system integration, moving beyond simple CRUD operations to offer a robust, version-controlled environment for your data models. By treating schemas as living, evolving constructs, Anansi enables systematic transformations while preserving system-wide data integrity. Its in-memory capabilities, backed by a persistent Git-enabled registry, ensure both high performance and reliable versioning for collaborative development and deployment. Anansi is ideal for organizations navigating complex, rapidly evolving, and distributed system architectures.
|
|
67
|
+
|
|
68
|
+
### Key Conceptual Components
|
|
69
|
+
|
|
70
|
+
- 👷 **Schema Registry**: Inspired by the intricate webs of knowledge, this component offers comprehensive metadata tracking and explicit dependency management for all your data schemas.
|
|
71
|
+
- 🧠 **Theoretical Migration Framework**: Provides formal methods for schema evolution, guaranteeing atomic transformations and supporting bidirectional migrations to ensure data consistency across versions.
|
|
72
|
+
- 🌐 **Architectural Abstractions**: Enables decoupled schema representation, cross-system consistency models, and adaptive persistence strategies to integrate seamlessly into diverse architectural landscapes.
|
|
73
|
+
|
|
74
|
+
### Key Features
|
|
75
|
+
|
|
76
|
+
- **Schema Definition & Validation**: Define complex data structures using a rich `SchemaDefinition` interface, complete with fields, nested schemas, constraints, and indexes. Leverage a powerful, auto-generated validation SDK to ensure data integrity against your defined schemas.
|
|
77
|
+
- **Schema Evolution & Migration**: Manage schema changes over time with a robust `MigrationEngine`. Define schema changes and data transformations to seamlessly evolve your data models forward and backward, preserving data consistency.
|
|
78
|
+
- **Flexible Persistence Adapters**: Interact with various data stores through a unified `Persistence` interface. Includes an in-memory ephemeral persistence layer for rapid prototyping and a production-ready PocketBase adapter with built-in retry mechanisms and error categorization.
|
|
79
|
+
- **Schema Registry with Version Control**: Store, manage, and version your schemas using a `SchemaRegistry` that can operate purely in-memory (backed by `LightningFS`) or integrate with Git for distributed version control, branching, and tagging.
|
|
80
|
+
- **Code Generation & Utilities**: Automatically generate TypeScript types (`schemaToTypes`) and human-readable documentation (`docgen`) directly from your `SchemaDefinition` files. Includes essential utilities for cryptographic hashing, deep merging, and JSON Patch operations.
|
|
81
|
+
- **Event-driven Data Operations**: Subscribe to granular persistence events (e.g., `create:success`, `update:failed`) and define triggers or scheduled tasks to automate workflows around data changes.
|
|
82
|
+
|
|
83
|
+
## Installation & Setup
|
|
84
|
+
|
|
85
|
+
### Prerequisites
|
|
86
|
+
|
|
87
|
+
- **Node.js**: Version 18.x or higher.
|
|
88
|
+
- **Bun**: Recommended for faster installation and script execution. (Alternatively, npm/yarn can be used).
|
|
89
|
+
- **TypeScript**: For developing with Anansi and its generated types.
|
|
90
|
+
- **PocketBase** (Optional): If using the PocketBase persistence adapter, a running PocketBase instance is required.
|
|
91
|
+
|
|
92
|
+
### Installation Steps
|
|
93
|
+
|
|
94
|
+
Install Anansi into your project using Bun (recommended) or npm:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Using Bun
|
|
98
|
+
bun add @asaidimu/anansi
|
|
99
|
+
|
|
100
|
+
# Using npm
|
|
101
|
+
npm install @asaidimu/anansi
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Configuration
|
|
105
|
+
|
|
106
|
+
#### Ephemeral Persistence (In-memory)
|
|
107
|
+
|
|
108
|
+
For local development and testing, you can use the ephemeral persistence layer which requires no external configuration:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { createEphemeralPersistence } from '@asaidimu/anansi';
|
|
112
|
+
|
|
113
|
+
// Define your predicate map (if using custom constraints)
|
|
114
|
+
const myPredicates = {
|
|
115
|
+
isPositive: ({ data, field, arguments: min }) => data[field] > min,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Create a new in-memory persistence instance
|
|
119
|
+
const persistence = createEphemeralPersistence({}, myPredicates); // No functionMap needed for basic use
|
|
120
|
+
|
|
121
|
+
// Now you can create and manage collections in memory
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Git-backed Schema Registry
|
|
125
|
+
|
|
126
|
+
To enable persistent, version-controlled schema management, you'll configure `createGitSchemaRegistry` with a remote Git repository (e.g., GitHub, Gitea). This will require authentication credentials.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { createGitSchemaRegistry, createGithubRepository } from '@asaidimu/anansi';
|
|
130
|
+
|
|
131
|
+
// Example for GitHub
|
|
132
|
+
const remoteRepo = await createGithubRepository({
|
|
133
|
+
username: process.env.GITHUB_USERNAME!,
|
|
134
|
+
password: process.env.GITHUB_PAT!, // Personal Access Token
|
|
135
|
+
repository: 'my-anansi-schemas',
|
|
136
|
+
create: true, // Auto-create if not exists
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const gitRegistry = await createGitSchemaRegistry('/my-schema-registry', {
|
|
140
|
+
remote: remoteRepo,
|
|
141
|
+
author: { name: 'Anansi Bot', email: 'anansi-bot@example.com' },
|
|
142
|
+
// proxy: 'https://cors.isomorphic-git.org', // Uncomment if encountering CORS issues
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await gitRegistry.init(); // Initialize the local Git repository and sync with remote
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### PocketBase Persistence
|
|
149
|
+
|
|
150
|
+
To use PocketBase, you need to provide the PocketBase URL and optionally an auth token:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { createPocketBasePersistence } from '@asaidimu/anansi/sdk/pocketbase'; // Note: path to specific SDK
|
|
154
|
+
import { createSchemaMigrationHelper } from '@asaidimu/anansi/lib/schema/helpers'; // Note: direct import for helper
|
|
155
|
+
|
|
156
|
+
const pocketBasePersistence = createPocketBasePersistence({
|
|
157
|
+
url: 'http://127.0.0.1:8090', // Your PocketBase instance URL
|
|
158
|
+
// authToken: 'YOUR_POCKETBASE_AUTH_TOKEN', // Optional: if auth is required
|
|
159
|
+
env: 'development', // 'development' for migrations/rollbacks, 'production' for read-only
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Use pocketBasePersistence like any other Anansi persistence instance
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Usage Documentation
|
|
166
|
+
|
|
167
|
+
### Defining a Schema
|
|
168
|
+
|
|
169
|
+
Schemas are defined using the `SchemaDefinition` interface. Here's a basic example:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { SchemaDefinition } from '@asaidimu/anansi';
|
|
173
|
+
|
|
174
|
+
const UserSchema: SchemaDefinition = {
|
|
175
|
+
name: "User",
|
|
176
|
+
version: "1.0.0",
|
|
177
|
+
description: "Represents a user in the system",
|
|
178
|
+
fields: {
|
|
179
|
+
id: { name: "id", type: "string", required: true, description: "Unique user ID" },
|
|
180
|
+
email: {
|
|
181
|
+
name: "email",
|
|
182
|
+
type: "string",
|
|
183
|
+
required: true,
|
|
184
|
+
constraints: [{
|
|
185
|
+
name: "isEmail",
|
|
186
|
+
predicate: "isEmailFormat", // Assumes 'isEmailFormat' is in your predicateMap
|
|
187
|
+
parameters: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
188
|
+
errorMessage: "Must be a valid email address."
|
|
189
|
+
}],
|
|
190
|
+
description: "User's email address"
|
|
191
|
+
},
|
|
192
|
+
status: {
|
|
193
|
+
name: "status",
|
|
194
|
+
type: "enum",
|
|
195
|
+
values: ["active", "inactive", "pending"],
|
|
196
|
+
default: "pending",
|
|
197
|
+
description: "User's current status"
|
|
198
|
+
},
|
|
199
|
+
profile: {
|
|
200
|
+
name: "profile",
|
|
201
|
+
type: "object",
|
|
202
|
+
schema: { id: "UserProfile" }, // References a nested schema
|
|
203
|
+
required: false,
|
|
204
|
+
description: "User's profile details"
|
|
205
|
+
},
|
|
206
|
+
roles: {
|
|
207
|
+
name: "roles",
|
|
208
|
+
type: "set",
|
|
209
|
+
itemsType: "string",
|
|
210
|
+
description: "Set of unique roles assigned to the user"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
nestedSchemas: {
|
|
214
|
+
UserProfile: {
|
|
215
|
+
name: "UserProfile",
|
|
216
|
+
fields: {
|
|
217
|
+
firstName: { name: "firstName", type: "string", required: true },
|
|
218
|
+
lastName: { name: "lastName", type: "string", required: true },
|
|
219
|
+
age: { name: "age", type: "number", required: false, default: 18 }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
indexes: [
|
|
224
|
+
{ name: "emailIndex", fields: ["email"], type: "unique", description: "Ensures unique email addresses" }
|
|
225
|
+
],
|
|
226
|
+
constraints: [
|
|
227
|
+
{
|
|
228
|
+
name: "ageConstraint",
|
|
229
|
+
operator: "and",
|
|
230
|
+
rules: [
|
|
231
|
+
{ name: "minAge", predicate: "min", field: "profile.age", parameters: 18 },
|
|
232
|
+
{ name: "maxAge", predicate: "max", field: "profile.age", parameters: 120 }
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
mock: (faker) => ({
|
|
237
|
+
id: faker.string.uuid(),
|
|
238
|
+
email: faker.internet.email(),
|
|
239
|
+
status: faker.helpers.arrayElement(["active", "inactive", "pending"]),
|
|
240
|
+
profile: {
|
|
241
|
+
firstName: faker.person.firstName(),
|
|
242
|
+
lastName: faker.person.lastName(),
|
|
243
|
+
age: faker.number.int({ min: 18, max: 99 })
|
|
244
|
+
},
|
|
245
|
+
roles: faker.helpers.arrayElements(["admin", "user", "guest"], { min: 1, max: 3 })
|
|
246
|
+
})
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Ephemeral Persistence
|
|
251
|
+
|
|
252
|
+
Interact with data using the in-memory persistence layer:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { createEphemeralPersistence, SchemaDefinition } from '@asaidimu/anansi';
|
|
256
|
+
|
|
257
|
+
const myPredicates = {
|
|
258
|
+
isEmailFormat: ({ data, field, arguments: regex }) => regex.test(data[field]),
|
|
259
|
+
min: ({ data, field, arguments: minValue }) => data[field] >= minValue,
|
|
260
|
+
max: ({ data, field, arguments: maxValue }) => data[field] <= maxValue,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const persistence = createEphemeralPersistence({}, myPredicates);
|
|
264
|
+
|
|
265
|
+
// Assume UserSchema is defined as above
|
|
266
|
+
const usersCollection = await persistence.createCollection<typeof UserSchema>({
|
|
267
|
+
name: UserSchema.name,
|
|
268
|
+
version: UserSchema.version,
|
|
269
|
+
fields: UserSchema.fields,
|
|
270
|
+
nestedSchemas: UserSchema.nestedSchemas,
|
|
271
|
+
constraints: UserSchema.constraints,
|
|
272
|
+
indexes: UserSchema.indexes,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// 🚀 Create
|
|
276
|
+
const newUser = await usersCollection.create({
|
|
277
|
+
data: {
|
|
278
|
+
id: 'user123',
|
|
279
|
+
email: 'test@example.com',
|
|
280
|
+
status: 'active',
|
|
281
|
+
profile: { firstName: 'John', lastName: 'Doe', age: 30 },
|
|
282
|
+
roles: ['admin', 'user'],
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
console.log('Created user:', newUser);
|
|
286
|
+
|
|
287
|
+
// ⚡ Read
|
|
288
|
+
const activeUsers = await usersCollection.read({
|
|
289
|
+
query: { filters: { status: { $eq: 'active' } } },
|
|
290
|
+
});
|
|
291
|
+
console.log('Active users:', activeUsers);
|
|
292
|
+
|
|
293
|
+
// 🔄 Update
|
|
294
|
+
const updatedUsers = await usersCollection.update({
|
|
295
|
+
query: { email: { $eq: 'test@example.com' } },
|
|
296
|
+
data: { status: 'inactive' },
|
|
297
|
+
});
|
|
298
|
+
console.log('Updated users:', updatedUsers);
|
|
299
|
+
|
|
300
|
+
// 🗑️ Delete
|
|
301
|
+
const deletedCount = await usersCollection.delete({
|
|
302
|
+
query: { id: { $eq: 'user123' } },
|
|
303
|
+
});
|
|
304
|
+
console.log('Deleted users count:', deletedCount);
|
|
305
|
+
|
|
306
|
+
// ✨ Validate
|
|
307
|
+
const invalidUser = { id: 'invalid', email: 'bad-email' };
|
|
308
|
+
const validationResult = usersCollection.validate(invalidUser);
|
|
309
|
+
console.log('Validation issues:', validationResult.issues);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Schema Registry (Git-backed)
|
|
313
|
+
|
|
314
|
+
Manage your schemas persistently with Git integration.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import {
|
|
318
|
+
createGitSchemaRegistry,
|
|
319
|
+
createGithubRepository,
|
|
320
|
+
SchemaDefinition,
|
|
321
|
+
} from '@asaidimu/anansi';
|
|
322
|
+
import { UserSchema } from './my-schemas'; // Assuming UserSchema is defined
|
|
323
|
+
|
|
324
|
+
// Configure remote repository (e.g., GitHub)
|
|
325
|
+
const githubRepo = await createGithubRepository({
|
|
326
|
+
username: 'your-github-username',
|
|
327
|
+
password: 'your-github-pat', // Use a Personal Access Token
|
|
328
|
+
repository: 'anansi-schemas',
|
|
329
|
+
create: true, // Create the repo if it doesn't exist
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Create Git-backed schema registry
|
|
333
|
+
const registry = await createGitSchemaRegistry('/anansi-schemas', {
|
|
334
|
+
remote: githubRepo,
|
|
335
|
+
author: { name: 'Anansi Bot', email: 'bot@anansi.com' },
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Initialize the registry (clones/creates local repo, syncs)
|
|
339
|
+
await registry.init();
|
|
340
|
+
|
|
341
|
+
// 👷 Create a schema
|
|
342
|
+
await registry.create({ schema: UserSchema });
|
|
343
|
+
console.log(`Schema '${UserSchema.name}' created/updated in registry.`);
|
|
344
|
+
|
|
345
|
+
// 📋 List all schemas
|
|
346
|
+
const allSchemas = await registry.list();
|
|
347
|
+
console.log('All schemas:', allSchemas);
|
|
348
|
+
|
|
349
|
+
// 🔍 Retrieve a schema definition
|
|
350
|
+
const retrievedSchema = await registry.schema({ name: 'User' });
|
|
351
|
+
console.log('Retrieved User schema:', retrievedSchema?.version);
|
|
352
|
+
|
|
353
|
+
// 🔄 Update a schema
|
|
354
|
+
const updatedUserSchema: SchemaDefinition = {
|
|
355
|
+
...UserSchema,
|
|
356
|
+
version: "1.1.0",
|
|
357
|
+
fields: {
|
|
358
|
+
...UserSchema.fields,
|
|
359
|
+
phone: { name: "phone", type: "string", required: false, description: "User's phone number" }
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
await registry.update({ schema: updatedUserSchema });
|
|
363
|
+
console.log(`Schema '${updatedUserSchema.name}' updated to v${updatedUserSchema.version}.`);
|
|
364
|
+
|
|
365
|
+
// ⚡ Sync local changes with remote Git repository
|
|
366
|
+
await registry.sync();
|
|
367
|
+
console.log('Registry synced with remote Git repository.');
|
|
368
|
+
|
|
369
|
+
// 🗑️ Delete a schema
|
|
370
|
+
// await registry.delete({ name: 'User' });
|
|
371
|
+
// console.log(`Schema 'User' deleted.`);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Schema Migration
|
|
375
|
+
|
|
376
|
+
Apply and roll back schema changes, including data transformations.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { createEphemeralPersistence, createSchemaMigrationHelper, SchemaDefinition, DataTransform } from '@asaidimu/anansi';
|
|
380
|
+
|
|
381
|
+
// Assume UserSchema is defined as above
|
|
382
|
+
const oldUserSchema: SchemaDefinition = {
|
|
383
|
+
...UserSchema,
|
|
384
|
+
version: "1.0.0", // Original version
|
|
385
|
+
fields: {
|
|
386
|
+
id: { name: "id", type: "string", required: true },
|
|
387
|
+
oldEmail: { name: "oldEmail", type: "string", required: false } // Field to be migrated
|
|
388
|
+
},
|
|
389
|
+
nestedSchemas: {}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const persistence = createEphemeralPersistence({}, {});
|
|
393
|
+
const usersCollection = await persistence.createCollection<any>({
|
|
394
|
+
name: oldUserSchema.name,
|
|
395
|
+
version: oldUserSchema.version,
|
|
396
|
+
fields: oldUserSchema.fields,
|
|
397
|
+
nestedSchemas: oldUserSchema.nestedSchemas
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Add some dummy data with oldEmail
|
|
401
|
+
await usersCollection.create({
|
|
402
|
+
data: [
|
|
403
|
+
{ id: 'u1', oldEmail: 'user1@old.com', status: 'active' },
|
|
404
|
+
{ id: 'u2', oldEmail: 'user2@old.com', status: 'inactive' }
|
|
405
|
+
]
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Define the migration
|
|
409
|
+
const addEmailMigration = (h: ReturnType<typeof createSchemaMigrationHelper>) => {
|
|
410
|
+
// Define forward transformation: oldEmail -> email
|
|
411
|
+
const forwardTransform: DataTransform<any, any>['forward'] = (data) => ({
|
|
412
|
+
...data,
|
|
413
|
+
email: data.oldEmail,
|
|
414
|
+
oldEmail: undefined // Remove old field
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Define backward transformation: email -> oldEmail
|
|
418
|
+
const backwardTransform: DataTransform<any, any>['backward'] = (data) => ({
|
|
419
|
+
...data,
|
|
420
|
+
oldEmail: data.email,
|
|
421
|
+
email: undefined // Remove new field
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
h.addField('email', { name: 'email', type: 'string', required: true, description: 'New email field' });
|
|
425
|
+
h.removeField('oldEmail'); // Deprecate/remove old field
|
|
426
|
+
|
|
427
|
+
return { forward: forwardTransform, backward: backwardTransform };
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// ➡️ Perform a dry run migration
|
|
431
|
+
console.log('\n--- Dry Run Migration (Forward) ---');
|
|
432
|
+
const { newSchema: dryRunSchema, dataPreview } = await usersCollection.migrate(
|
|
433
|
+
'Migrate oldEmail to email',
|
|
434
|
+
addEmailMigration,
|
|
435
|
+
true // dryRun = true
|
|
436
|
+
);
|
|
437
|
+
console.log('Dry run new schema version:', dryRunSchema.version);
|
|
438
|
+
const previewRecords = await new Response(dataPreview).json(); // Read from stream
|
|
439
|
+
console.log('Dry run data preview:', previewRecords);
|
|
440
|
+
|
|
441
|
+
// 🚀 Apply the actual migration
|
|
442
|
+
console.log('\n--- Applying Migration (Forward) ---');
|
|
443
|
+
await usersCollection.migrate(
|
|
444
|
+
'Migrate oldEmail to email',
|
|
445
|
+
addEmailMigration,
|
|
446
|
+
false // dryRun = false
|
|
447
|
+
);
|
|
448
|
+
console.log('Migration applied. Current schema version:', usersCollection.schema().version);
|
|
449
|
+
const migratedData = await usersCollection.read({});
|
|
450
|
+
console.log('Migrated data:', migratedData);
|
|
451
|
+
|
|
452
|
+
// ⏪ Rollback the migration (if supported by persistence)
|
|
453
|
+
console.log('\n--- Rolling Back Migration ---');
|
|
454
|
+
await usersCollection.rollback(undefined, false); // Rollback to previous version
|
|
455
|
+
console.log('Rolled back. Current schema version:', usersCollection.schema().version);
|
|
456
|
+
const rolledBackData = await usersCollection.read({});
|
|
457
|
+
console.log('Rolled back data:', rolledBackData);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Type Generation
|
|
461
|
+
|
|
462
|
+
Generate TypeScript types from your schema definitions.
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
import { schemaToTypes, SchemaDefinition } from '@asaidimu/anansi';
|
|
466
|
+
|
|
467
|
+
const ProductSchema: SchemaDefinition = {
|
|
468
|
+
name: "Product",
|
|
469
|
+
version: "1.0.0",
|
|
470
|
+
fields: {
|
|
471
|
+
id: { name: "id", type: "string", required: true },
|
|
472
|
+
name: { name: "name", type: "string", required: true },
|
|
473
|
+
price: { name: "price", type: "number", required: true },
|
|
474
|
+
currency: { name: "currency", type: "enum", values: ["USD", "EUR", "GBP"] },
|
|
475
|
+
details: {
|
|
476
|
+
name: "details",
|
|
477
|
+
type: "object",
|
|
478
|
+
schema: { id: "ProductDetails" }
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
nestedSchemas: {
|
|
482
|
+
ProductDetails: {
|
|
483
|
+
name: "ProductDetails",
|
|
484
|
+
fields: {
|
|
485
|
+
weight: { name: "weight", type: "number" },
|
|
486
|
+
dimensions: {
|
|
487
|
+
name: "dimensions",
|
|
488
|
+
type: "object",
|
|
489
|
+
schema: { id: "ProductDimensions" }
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
ProductDimensions: {
|
|
494
|
+
name: "ProductDimensions",
|
|
495
|
+
fields: {
|
|
496
|
+
length: { name: "length", type: "number" },
|
|
497
|
+
width: { name: "width", type: "number" },
|
|
498
|
+
height: { name: "height", type: "number" }
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const generatedTypes = schemaToTypes(ProductSchema);
|
|
505
|
+
console.log(generatedTypes);
|
|
506
|
+
|
|
507
|
+
/*
|
|
508
|
+
// Output will be similar to:
|
|
509
|
+
export type ProductDimensions = {
|
|
510
|
+
length?: number;
|
|
511
|
+
width?: number;
|
|
512
|
+
height?: number;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
export type ProductDetails = {
|
|
516
|
+
weight?: number;
|
|
517
|
+
dimensions?: ProductDimensions;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
export type ProductCurrency = "USD" | "EUR" | "GBP";
|
|
521
|
+
|
|
522
|
+
export type Product = {
|
|
523
|
+
id: string;
|
|
524
|
+
name: string;
|
|
525
|
+
price: number;
|
|
526
|
+
currency: ProductCurrency;
|
|
527
|
+
details?: string | ProductDetails; // "string" for concrete schemas reference
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
export enum ProductIndexNames {
|
|
531
|
+
...
|
|
532
|
+
}
|
|
533
|
+
*/
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Documentation Generation
|
|
537
|
+
|
|
538
|
+
Generate markdown documentation for your schemas.
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
import { docgen, SchemaDefinition } from '@asaidimu/anansi';
|
|
542
|
+
import { faker } from '@faker-js/faker';
|
|
543
|
+
|
|
544
|
+
// Assume ProductSchema is defined as above
|
|
545
|
+
|
|
546
|
+
const productDoc = docgen(ProductSchema, { faker });
|
|
547
|
+
console.log(productDoc);
|
|
548
|
+
|
|
549
|
+
/*
|
|
550
|
+
// Output will be similar to:
|
|
551
|
+
# Product Schema (Version 1.0.0)
|
|
552
|
+
|
|
553
|
+
## Metadata
|
|
554
|
+
- **Dependencies:** None
|
|
555
|
+
- **Created:** 2024-01-01T00:00:00.000Z
|
|
556
|
+
|
|
557
|
+
## Fields
|
|
558
|
+
|
|
559
|
+
| Name | Type | Required | Default | Description | Deprecated | Unique | Constraints |
|
|
560
|
+
|----------|--------|----------|---------|--------------------|------------|--------|-------------|
|
|
561
|
+
| id | string | Yes | `None` | | No | No | 0 |
|
|
562
|
+
| name | string | Yes | `None` | | No | No | 0 |
|
|
563
|
+
| price | number | Yes | `None` | | No | No | 0 |
|
|
564
|
+
| currency | enum | Yes | `"USD"` | | No | No | 0 |
|
|
565
|
+
| details | object | No | `None` | | No | No | 0 |
|
|
566
|
+
|
|
567
|
+
### Nested Schema: ProductDetails
|
|
568
|
+
|
|
569
|
+
#### weight (number)
|
|
570
|
+
|
|
571
|
+
**Required:** No
|
|
572
|
+
|
|
573
|
+
#### dimensions (object)
|
|
574
|
+
|
|
575
|
+
##### length (number)
|
|
576
|
+
|
|
577
|
+
**Required:** No
|
|
578
|
+
|
|
579
|
+
##### width (number)
|
|
580
|
+
|
|
581
|
+
**Required:** No
|
|
582
|
+
|
|
583
|
+
##### height (number)
|
|
584
|
+
|
|
585
|
+
**Required:** No
|
|
586
|
+
|
|
587
|
+
## Indexes
|
|
588
|
+
|
|
589
|
+
| Name | Type | Fields | Unique | Order | Partial Condition | Description |
|
|
590
|
+
|------|--------|---------|--------|-------|-------------------|-------------|
|
|
591
|
+
| ... | ... | ... | ... | ... | ... | ... |
|
|
592
|
+
|
|
593
|
+
## Constraints
|
|
594
|
+
### Schema-level Constraints
|
|
595
|
+
...
|
|
596
|
+
|
|
597
|
+
## Migrations
|
|
598
|
+
|
|
599
|
+
| ID | Description | Status | Changes |
|
|
600
|
+
|----|-------------|--------|---------|
|
|
601
|
+
| ...| ... | ... | ... |
|
|
602
|
+
|
|
603
|
+
## Example Data
|
|
604
|
+
```json
|
|
605
|
+
{
|
|
606
|
+
"id": "e221b3a4-c5d6-7890-a1b2-c3d4e5f67890",
|
|
607
|
+
"name": "Ergonomic Widget",
|
|
608
|
+
"price": 99.99,
|
|
609
|
+
"currency": "USD",
|
|
610
|
+
"details": {
|
|
611
|
+
"weight": 0.5,
|
|
612
|
+
"dimensions": {
|
|
613
|
+
"length": 10,
|
|
614
|
+
"width": 5,
|
|
615
|
+
"height": 2
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
*/
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Project Architecture
|
|
623
|
+
|
|
624
|
+
Anansi is structured to provide a modular and extensible platform for data model management.
|
|
625
|
+
|
|
626
|
+
### Directory Structure
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
.
|
|
630
|
+
├── src/
|
|
631
|
+
│ ├── lib/ # Core libraries for persistence, registry, migration, schema
|
|
632
|
+
│ │ ├── persistence/ # In-memory and adapter-based data persistence (EmphemeralCollection)
|
|
633
|
+
│ │ ├── registry/ # Schema Registry (LightningFS + Git integration)
|
|
634
|
+
│ │ ├── migration/ # Schema migration engine (MigrationEngine)
|
|
635
|
+
│ │ └── schema/ # Schema validation, helpers, and utilities
|
|
636
|
+
│ ├── sdk/ # Specific SDK implementations (e.g., PocketBase adapter, static validators)
|
|
637
|
+
│ ├── types/ # Core TypeScript interfaces (SchemaDefinition, Persistence, Migration, etc.)
|
|
638
|
+
│ └── tools/ # General utilities (crypto, merge, patch, typegen, docgen, validator, version)
|
|
639
|
+
├── docs/ # VitePress documentation site
|
|
640
|
+
├── tests/ # Unit and integration tests
|
|
641
|
+
├── public/ # Public assets for UI (e.g., Vite SVG)
|
|
642
|
+
├── index.ts # Main entry point for the Anansi library
|
|
643
|
+
├── package.json # Project metadata and dependencies
|
|
644
|
+
├── dist.package.json # Package.json for distributed npm package
|
|
645
|
+
├── vitest.config.ts # Vitest configuration for testing
|
|
646
|
+
├── vite.config.ts # Vite configuration for UI development
|
|
647
|
+
└── tsconfig.json # TypeScript configuration
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Core Components
|
|
651
|
+
|
|
652
|
+
- **`SchemaDefinition` (`src/types/schema-definition.ts`)**: The central contract defining the structure of data models, including fields, nested schemas, constraints, and migrations.
|
|
653
|
+
- **`Persistence` (`src/types/persistence.ts`)**: An abstract interface for all data storage operations (create, read, update, delete, subscribe), designed for pluggable backends.
|
|
654
|
+
- **`EmphemeralCollection` (`src/lib/persistence/collection.ts`)**: An in-memory implementation of `PersistenceCollection` for rapid development.
|
|
655
|
+
- **PocketBase Adapter (`src/sdk/pocketbase/index.tsx`)**: A concrete `Persistence` implementation for PocketBase, handling schema-driven collection management, migrations, and eventing.
|
|
656
|
+
- **`MigrationEngine` (`src/lib/migration/index.ts`)**: Manages the evolution of schemas over time, applying schema changes and data transformations in a controlled, versioned manner.
|
|
657
|
+
- **`SchemaRegistry` (`src/lib/registry/registry.ts`)**: The core component for storing and versioning schema definitions. It uses `LightningFS` for in-memory storage.
|
|
658
|
+
- **`createGitSchemaRegistry` (`src/lib/registry/git-registry.ts`)**: A factory function that wraps `SchemaRegistry` with `isomorphic-git` to provide persistent, distributed version control for schemas.
|
|
659
|
+
- **`Validators` (`src/tools/validator.ts`, `src/lib/schema/validators.ts`)**: Provides utilities for validating data against `SchemaDefinition` rules and for validating the schema definitions themselves.
|
|
660
|
+
- **`Type Generators` (`src/tools/typegen.ts`)**: Automatically generates TypeScript type definitions from `SchemaDefinition` files, ensuring strict type safety across your codebase.
|
|
661
|
+
- **`Documentation Generator` (`src/tools/docgen.ts`)**: Generates human-readable Markdown documentation for schemas, including fields, indexes, constraints, and example mock data.
|
|
662
|
+
|
|
663
|
+
### Data Flow
|
|
664
|
+
|
|
665
|
+
1. **Schema Definition**: Developers define data models using the `SchemaDefinition` interface.
|
|
666
|
+
2. **Schema Registration**: Schemas are added to the `SchemaRegistry` (either in-memory or Git-backed) for version control and discovery.
|
|
667
|
+
3. **Persistence Layer**: A `Persistence` instance (e.g., `EphemeralPersistence`, `PocketBasePersistence`) is initialized with optional predicates and functions for query and validation.
|
|
668
|
+
4. **Collection Interaction**: Developers interact with collections (e.g., `usersCollection.create()`, `usersCollection.read()`) through the `PersistenceCollection` interface.
|
|
669
|
+
5. **Validation**: All data operations trigger internal validation against the collection's `SchemaDefinition` using the `createStandardSchemaValidator`.
|
|
670
|
+
6. **Schema Evolution**: When data models change, `SchemaChanges` are defined, and the `MigrationEngine` applies these changes to both the schema definition and existing data.
|
|
671
|
+
7. **Synchronization (Git)**: For Git-backed registries, changes are committed and pushed, enabling collaborative schema evolution and traceability.
|
|
672
|
+
|
|
673
|
+
### Extension Points
|
|
674
|
+
|
|
675
|
+
- **Custom Persistence Adapters**: Implement the `Persistence` and `PersistenceCollection` interfaces to integrate Anansi with any new data storage backend.
|
|
676
|
+
- **Custom Predicates**: Define custom validation logic (e.g., `isEmailFormat`, `isStrongPassword`) and provide them to `createEphemeralPersistence` or any other persistence implementation.
|
|
677
|
+
- **Data Transforms**: Write custom `forward` and `backward` transformation functions for migrations, enabling complex data shape changes between schema versions.
|
|
678
|
+
- **Schema Hints**: Extend `InputHint` and `SchemaHint` to guide UI generation or other tooling based on schema metadata.
|
|
679
|
+
|
|
680
|
+
## Development & Contributing
|
|
681
|
+
|
|
682
|
+
### Development Setup
|
|
683
|
+
|
|
684
|
+
To set up the project for local development:
|
|
685
|
+
|
|
686
|
+
1. **Clone the repository:**
|
|
687
|
+
```bash
|
|
688
|
+
git clone https://github.com/asaidimu/data-model.git anansi
|
|
689
|
+
cd anansi
|
|
690
|
+
```
|
|
691
|
+
2. **Install dependencies using Bun (recommended):**
|
|
692
|
+
```bash
|
|
693
|
+
bun install
|
|
694
|
+
```
|
|
695
|
+
If you don't have Bun, you can use npm:
|
|
696
|
+
```bash
|
|
697
|
+
npm install
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Available Scripts
|
|
701
|
+
|
|
702
|
+
The `package.json` includes several scripts for development workflows:
|
|
17
703
|
|
|
18
|
-
|
|
704
|
+
- `bun ci`: Installs dependencies.
|
|
705
|
+
- `bun clean`: Removes the `dist` directory.
|
|
706
|
+
- `bun prebuild`: Cleans and runs `./.sync-package.ts`.
|
|
707
|
+
- `bun build`: Compiles TypeScript files to `dist/` for CJS and ESM formats, generates declaration files, and minifies.
|
|
708
|
+
- `bun build:watch`: Runs `build` in watch mode for continuous compilation.
|
|
709
|
+
- `bun postbuild`: Copies `README.md`, `LICENSE.md`, and `dist.package.json` into the `dist` directory.
|
|
710
|
+
- `bun test`: Runs unit and integration tests using Vitest.
|
|
711
|
+
- `bun test:ci`: Runs Vitest tests in CI mode (runs once, exits).
|
|
712
|
+
- `bun test:debug`: Runs Vitest with debugger attached.
|
|
713
|
+
- `bun docs:dev`: Starts the VitePress development server for documentation.
|
|
714
|
+
- `bun docs:build`: Builds the static VitePress documentation site.
|
|
715
|
+
- `bun ui:dev`: Starts the Vite development server for the example UI.
|
|
716
|
+
- `bun docs:preview`: Previews the built documentation site.
|
|
19
717
|
|
|
20
|
-
|
|
718
|
+
### Testing
|
|
21
719
|
|
|
22
|
-
|
|
23
|
-
- Modeled schema transformations
|
|
24
|
-
- Rigorous state transition modeling
|
|
25
|
-
- Formal methods for ensuring data consistency
|
|
720
|
+
Anansi uses [Vitest](https://vitest.dev/) for its test suite.
|
|
26
721
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
722
|
+
To run all tests:
|
|
723
|
+
```bash
|
|
724
|
+
bun test
|
|
725
|
+
```
|
|
31
726
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
727
|
+
To run tests in CI mode (non-interactive):
|
|
728
|
+
```bash
|
|
729
|
+
bun test:ci
|
|
730
|
+
```
|
|
36
731
|
|
|
37
|
-
|
|
732
|
+
Tests include coverage checks and are configured to run in a `happy-dom` environment with `fake-indexeddb` for browser-like filesystem operations (`LightningFS`).
|
|
38
733
|
|
|
39
|
-
|
|
40
|
-
- Inspired by the intricate webs of knowledge
|
|
41
|
-
- Comprehensive metadata tracking
|
|
42
|
-
- Explicit dependency management
|
|
734
|
+
### Contributing Guidelines
|
|
43
735
|
|
|
44
|
-
|
|
45
|
-
- Formal methods for schema evolution
|
|
46
|
-
- Atomic transformation guarantees
|
|
47
|
-
- Bidirectional migration support
|
|
736
|
+
We welcome contributions! Please follow these guidelines:
|
|
48
737
|
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
738
|
+
1. **Fork** the repository and **clone** your fork.
|
|
739
|
+
2. Create a **new branch** for your feature or bug fix: `git checkout -b feature/my-new-feature` or `bugfix/fix-some-bug`.
|
|
740
|
+
3. Make your changes, ensuring code adheres to existing style and conventions.
|
|
741
|
+
4. Write or update **tests** for your changes to ensure proper functionality and maintain test coverage.
|
|
742
|
+
5. Ensure all tests pass (`bun test`).
|
|
743
|
+
6. Use **semantic commit messages** (e.g., `feat: add new feature`, `fix: resolve bug`). This project uses `semantic-release`.
|
|
744
|
+
7. **Open a Pull Request** to the `main` branch of the upstream repository.
|
|
53
745
|
|
|
54
|
-
|
|
746
|
+
### Issue Reporting
|
|
55
747
|
|
|
56
|
-
|
|
57
|
-
- Temporal consistency modeling
|
|
58
|
-
- Causal dependency tracking
|
|
59
|
-
- Atomic migration operation design
|
|
60
|
-
- Abstract representation of complex data relationships
|
|
748
|
+
For bugs, feature requests, or questions, please open an issue on our [GitHub Issues page](https://github.com/asaidimu/data-model/issues).
|
|
61
749
|
|
|
62
|
-
##
|
|
750
|
+
## Additional Information
|
|
63
751
|
|
|
64
|
-
|
|
65
|
-
- Highly complex enterprise architectures
|
|
66
|
-
- Rapidly evolving business requirements
|
|
67
|
-
- Multi-service, distributed system challenges
|
|
68
|
-
- Strict regulatory and data integrity requirements
|
|
752
|
+
### Troubleshooting
|
|
69
753
|
|
|
70
|
-
|
|
754
|
+
- **`Buffer is not defined` error**: If running in a browser environment, ensure that `window.Buffer = Buffer;` is included as done in `src/lib/registry/registry.ts`.
|
|
755
|
+
- **CORS issues with `isomorphic-git` or PocketBase**: If interacting with remote Git repositories or PocketBase from a browser, you might need a CORS proxy. Configure `createGitSchemaRegistry` or `createPocketBasePersistence` with a `proxy` URL.
|
|
756
|
+
- **Migration `TRANSFORM_ERROR`**: Ensure your `DataTransform` functions are correctly defined and handle all expected input shapes. For remote transforms, verify the URL and module export.
|
|
757
|
+
- **Git `fastForwardOnly` errors**: When performing `git pull` or `git push`, if `fastForwardOnly` is enabled and conflicts exist, the operation might fail. Consider resolving conflicts manually or disable `fastForwardOnly` if acceptable for your workflow.
|
|
71
758
|
|
|
72
|
-
|
|
73
|
-
- ✅ Systematic approach to system evolution
|
|
74
|
-
- ✅ Comprehensive architectural flexibility
|
|
75
|
-
- ✅ Rigorous data integrity preservation
|
|
76
|
-
- ✅ Advanced theoretical foundations
|
|
759
|
+
### FAQ
|
|
77
760
|
|
|
78
|
-
|
|
761
|
+
- **What is Anansi primarily designed for?**
|
|
762
|
+
Anansi is designed for managing complex enterprise data models, focusing on schema evolution, data integrity, and flexible persistence across distributed systems. It's a comprehensive toolkit, not just a simple ORM or validation library.
|
|
763
|
+
- **How does Anansi handle data transformations during migrations?**
|
|
764
|
+
Anansi uses `DataTransform` objects within migrations, which contain explicit `forward` and `backward` functions. These functions are executed on data streams to transform data shapes as the schema evolves.
|
|
765
|
+
- **Is Anansi production-ready?**
|
|
766
|
+
Yes, Anansi is built with production use cases in mind, emphasizing theoretical rigor, data integrity, and extensible architecture. The PocketBase adapter and Git-backed schema registry provide production-grade capabilities for persistence and version control.
|
|
767
|
+
- **Can I use Anansi with other databases?**
|
|
768
|
+
Yes, Anansi is designed with a pluggable `Persistence` interface. You can create custom adapters for any database or data source by implementing this interface.
|
|
79
769
|
|
|
80
|
-
|
|
81
|
-
- Conceptual frameworks
|
|
82
|
-
- Theoretical models
|
|
83
|
-
- Architectural guidelines
|
|
84
|
-
- Reference implementations
|
|
770
|
+
### Changelog
|
|
85
771
|
|
|
86
|
-
|
|
772
|
+
For a detailed history of changes, features, and bug fixes, please refer to the [CHANGELOG.md](CHANGELOG.md) file.
|
|
87
773
|
|
|
88
|
-
|
|
89
|
-
- Advanced schema management techniques
|
|
90
|
-
- Distributed system design principles
|
|
91
|
-
- Theoretical computer science applications in enterprise architecture
|
|
774
|
+
### License
|
|
92
775
|
|
|
93
|
-
|
|
776
|
+
This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.
|
|
94
777
|
|
|
95
|
-
|
|
96
|
-
- Academic researchers
|
|
97
|
-
- Enterprise architects
|
|
98
|
-
- Systems design professionals
|
|
778
|
+
### Acknowledgments
|
|
99
779
|
|
|
100
|
-
|
|
780
|
+
Anansi draws inspiration from and builds upon several foundational technologies and concepts:
|
|
101
781
|
|
|
102
|
-
|
|
782
|
+
- **SQL Pragmatism**: For the approach to schema definition, constraints, and indexing.
|
|
783
|
+
- **`isomorphic-git`**: For enabling Git operations in diverse JavaScript environments.
|
|
784
|
+
- **`LightningFS`**: For providing a performant in-memory filesystem abstraction.
|
|
785
|
+
- **PocketBase**: For its powerful real-time backend capabilities, integrated via a dedicated persistence adapter.
|
|
786
|
+
- **`@faker-js/faker`**: For robust mock data generation capabilities.
|
|
787
|
+
- **`@standard-schema/spec`**: For providing a standardized schema validation specification.
|
|
103
788
|
|
|
104
|
-
|
|
789
|
+
We are grateful to the creators and maintainers of these projects for their invaluable contributions to the open-source ecosystem.
|