@asaidimu/anansi 1.6.5 → 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.
Files changed (6) hide show
  1. package/README.md +750 -65
  2. package/index.cjs +13 -13
  3. package/index.d.cts +753 -173
  4. package/index.d.ts +753 -173
  5. package/index.js +14 -14
  6. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Anansi Schema Evolution Platform
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@asaidimu/anansi.svg)](https://www.npmjs.com/package/@asaidimu/anansi)
4
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/asaidimu/data-model/main.yml?branch=main&label=build)](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
- A comprehensive platform for solving complex enterprise data management challenges through a principled approach to schema design, evolution, and system integration.
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
- ## Core Philosophical Principles
718
+ ### Testing
21
719
 
22
- ### 1. Theoretical Precision
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
- ### 2. Holistic System Design
28
- - View schemas as living, evolving constructs
29
- - Provide a unified approach to data model management
30
- - Support complex enterprise architectural needs
722
+ To run all tests:
723
+ ```bash
724
+ bun test
725
+ ```
31
726
 
32
- ### 3. Systematic Evolution Strategy
33
- - Mathematically defined migration paths
34
- - Preservation of system-wide data integrity
35
- - Controlled, traceable schema transformations
727
+ To run tests in CI mode (non-interactive):
728
+ ```bash
729
+ bun test:ci
730
+ ```
36
731
 
37
- ## Key Conceptual Components
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
- - 👷 **Schema Registry**
40
- - Inspired by the intricate webs of knowledge
41
- - Comprehensive metadata tracking
42
- - Explicit dependency management
734
+ ### Contributing Guidelines
43
735
 
44
- - 🧠 **Theoretical Migration Framework**
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
- - 🌐 **Architectural Abstractions**
50
- - Decoupled schema representation
51
- - Cross-system consistency models
52
- - Adaptive persistence strategies
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
- ## Theoretical Strengths
746
+ ### Issue Reporting
55
747
 
56
- The library is built on advanced computational and systems theory:
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
- ## Target Domains
750
+ ## Additional Information
63
751
 
64
- Ideal for organizations facing:
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
- ## Conceptual Benefits
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
- - ✅ Sound schema management
73
- - ✅ Systematic approach to system evolution
74
- - ✅ Comprehensive architectural flexibility
75
- - ✅ Rigorous data integrity preservation
76
- - ✅ Advanced theoretical foundations
759
+ ### FAQ
77
760
 
78
- ## Architectural Approach
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
- Rather than a simple tool, this is a comprehensive library that provides:
81
- - Conceptual frameworks
82
- - Theoretical models
83
- - Architectural guidelines
84
- - Reference implementations
770
+ ### Changelog
85
771
 
86
- ## Research and Exploration
772
+ For a detailed history of changes, features, and bug fixes, please refer to the [CHANGELOG.md](CHANGELOG.md) file.
87
773
 
88
- The platform is a living research initiative exploring:
89
- - Advanced schema management techniques
90
- - Distributed system design principles
91
- - Theoretical computer science applications in enterprise architecture
774
+ ### License
92
775
 
93
- ## Collaboration and Research
776
+ This project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for details.
94
777
 
95
- In the spirit of Anansi's collaborative wisdom, we invite:
96
- - Academic researchers
97
- - Enterprise architects
98
- - Systems design professionals
778
+ ### Acknowledgments
99
779
 
100
- To explore, challenge, and extend the theoretical foundations of enterprise system evolution.
780
+ Anansi draws inspiration from and builds upon several foundational technologies and concepts:
101
781
 
102
- ## Documentation and Exploration
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
- Coming soon...
789
+ We are grateful to the creators and maintainers of these projects for their invaluable contributions to the open-source ecosystem.