pomegranate-db 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 +21 -0
- package/NOTICE.md +38 -0
- package/PomegranateDB.podspec +67 -0
- package/README.md +122 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
- package/dist/adapters/expo-sqlite/index.d.ts +2 -0
- package/dist/adapters/expo-sqlite/index.js +6 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
- package/dist/adapters/loki/LokiAdapter.js +144 -0
- package/dist/adapters/loki/index.d.ts +6 -0
- package/dist/adapters/loki/index.js +12 -0
- package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
- package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
- package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
- package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
- package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
- package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
- package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
- package/dist/adapters/loki/worker/loki.worker.js +112 -0
- package/dist/adapters/loki/worker/types.d.ts +44 -0
- package/dist/adapters/loki/worker/types.js +11 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
- package/dist/adapters/native-sqlite/index.d.ts +2 -0
- package/dist/adapters/native-sqlite/index.js +6 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
- package/dist/adapters/op-sqlite/index.d.ts +2 -0
- package/dist/adapters/op-sqlite/index.js +6 -0
- package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
- package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
- package/dist/adapters/sqlite/index.d.ts +2 -0
- package/dist/adapters/sqlite/index.js +6 -0
- package/dist/adapters/sqlite/sql.d.ts +35 -0
- package/dist/adapters/sqlite/sql.js +258 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.js +9 -0
- package/dist/collection/Collection.d.ts +103 -0
- package/dist/collection/Collection.js +245 -0
- package/dist/collection/index.d.ts +2 -0
- package/dist/collection/index.js +6 -0
- package/dist/database/Database.d.ts +128 -0
- package/dist/database/Database.js +245 -0
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.js +6 -0
- package/dist/encryption/index.d.ts +62 -0
- package/dist/encryption/index.js +276 -0
- package/dist/encryption/nodeCrypto.d.ts +18 -0
- package/dist/encryption/nodeCrypto.js +25 -0
- package/dist/encryption/nodeCrypto.native.d.ts +13 -0
- package/dist/encryption/nodeCrypto.native.js +26 -0
- package/dist/expo.d.ts +12 -0
- package/dist/expo.js +32 -0
- package/dist/hooks/index.d.ts +115 -0
- package/dist/hooks/index.js +285 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +57 -0
- package/dist/model/Model.d.ts +92 -0
- package/dist/model/Model.js +251 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.js +7 -0
- package/dist/observable/Subject.d.ts +60 -0
- package/dist/observable/Subject.js +132 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +10 -0
- package/dist/query/QueryBuilder.d.ts +51 -0
- package/dist/query/QueryBuilder.js +165 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +7 -0
- package/dist/query/types.d.ts +60 -0
- package/dist/query/types.js +9 -0
- package/dist/schema/builder.d.ts +68 -0
- package/dist/schema/builder.js +168 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +7 -0
- package/dist/schema/types.d.ts +108 -0
- package/dist/schema/types.js +9 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +6 -0
- package/dist/sync/sync.d.ts +15 -0
- package/dist/sync/sync.js +182 -0
- package/dist/sync/types.d.ts +41 -0
- package/dist/sync/types.js +6 -0
- package/dist/utils/index.d.ts +45 -0
- package/dist/utils/index.js +99 -0
- package/expo-plugin/index.d.ts +68 -0
- package/expo-plugin/index.js +83 -0
- package/native/android-jsi/build.gradle +45 -0
- package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
- package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
- package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
- package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
- package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
- package/native/ios/DatabasePlatformIOS.mm +83 -0
- package/native/ios/PomegranateJSI.h +15 -0
- package/native/ios/PomegranateJSI.mm +59 -0
- package/native/shared/Database.cpp +283 -0
- package/native/shared/Database.h +84 -0
- package/native/shared/Sqlite.cpp +61 -0
- package/native/shared/Sqlite.h +67 -0
- package/native/shared/sqlite3/sqlite3.c +260493 -0
- package/native/shared/sqlite3/sqlite3.h +13583 -0
- package/package.json +127 -0
- package/react-native.config.js +28 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.query = exports.QueryBuilder = void 0;
|
|
4
|
+
var QueryBuilder_1 = require("./QueryBuilder");
|
|
5
|
+
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return QueryBuilder_1.QueryBuilder; } });
|
|
6
|
+
Object.defineProperty(exports, "query", { enumerable: true, get: function () { return QueryBuilder_1.query; } });
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query descriptor types.
|
|
3
|
+
*
|
|
4
|
+
* Queries are built as plain descriptor objects (no classes), making them
|
|
5
|
+
* serializable and easy to translate to SQL or LokiJS query syntax.
|
|
6
|
+
*/
|
|
7
|
+
export type ComparisonOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'like' | 'notLike' | 'between' | 'isNull' | 'isNotNull';
|
|
8
|
+
export interface WhereClause {
|
|
9
|
+
readonly type: 'where';
|
|
10
|
+
readonly column: string;
|
|
11
|
+
readonly operator: ComparisonOperator;
|
|
12
|
+
readonly value: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface AndClause {
|
|
15
|
+
readonly type: 'and';
|
|
16
|
+
readonly conditions: readonly Condition[];
|
|
17
|
+
}
|
|
18
|
+
export interface OrClause {
|
|
19
|
+
readonly type: 'or';
|
|
20
|
+
readonly conditions: readonly Condition[];
|
|
21
|
+
}
|
|
22
|
+
export interface NotClause {
|
|
23
|
+
readonly type: 'not';
|
|
24
|
+
readonly condition: Condition;
|
|
25
|
+
}
|
|
26
|
+
export type Condition = WhereClause | AndClause | OrClause | NotClause;
|
|
27
|
+
export type SortOrder = 'asc' | 'desc';
|
|
28
|
+
export interface OrderByClause {
|
|
29
|
+
readonly column: string;
|
|
30
|
+
readonly order: SortOrder;
|
|
31
|
+
}
|
|
32
|
+
export interface JoinClause {
|
|
33
|
+
readonly table: string;
|
|
34
|
+
readonly leftColumn: string;
|
|
35
|
+
readonly rightColumn: string;
|
|
36
|
+
}
|
|
37
|
+
export interface QueryDescriptor {
|
|
38
|
+
readonly table: string;
|
|
39
|
+
readonly conditions: readonly Condition[];
|
|
40
|
+
readonly orderBy: readonly OrderByClause[];
|
|
41
|
+
readonly limit?: number;
|
|
42
|
+
readonly offset?: number;
|
|
43
|
+
readonly joins: readonly JoinClause[];
|
|
44
|
+
}
|
|
45
|
+
export interface SearchDescriptor {
|
|
46
|
+
readonly table: string;
|
|
47
|
+
readonly term: string;
|
|
48
|
+
readonly fields: readonly string[];
|
|
49
|
+
readonly conditions: readonly Condition[];
|
|
50
|
+
readonly orderBy: readonly OrderByClause[];
|
|
51
|
+
readonly limit: number;
|
|
52
|
+
readonly offset: number;
|
|
53
|
+
}
|
|
54
|
+
export type BatchOperationType = 'create' | 'update' | 'delete' | 'destroyPermanently';
|
|
55
|
+
export interface BatchOperation {
|
|
56
|
+
readonly type: BatchOperationType;
|
|
57
|
+
readonly table: string;
|
|
58
|
+
readonly rawRecord?: Record<string, unknown>;
|
|
59
|
+
readonly id?: string;
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Query descriptor types.
|
|
4
|
+
*
|
|
5
|
+
* Queries are built as plain descriptor objects (no classes), making them
|
|
6
|
+
* serializable and easy to translate to SQL or LokiJS query syntax.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema builder — the `m` API.
|
|
3
|
+
*
|
|
4
|
+
* Provides a fluent, Zod-inspired builder for defining model schemas
|
|
5
|
+
* with full TypeScript inference.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const PostSchema = m.model('posts', {
|
|
9
|
+
* title: m.text(),
|
|
10
|
+
* body: m.text(),
|
|
11
|
+
* isPinned: m.boolean().default(false),
|
|
12
|
+
* createdAt: m.date('created_at').readonly(),
|
|
13
|
+
* author: m.belongsTo('users', { key: 'author_id' }),
|
|
14
|
+
* comments: m.hasMany('comments', { foreignKey: 'post_id' }),
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
import type { ColumnDescriptor, TextColumn, NumberColumn, BooleanColumn, DateColumn, BelongsToDescriptor, HasManyDescriptor, ModelSchema, FieldDescriptor } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Fluent builder wrapping a ColumnDescriptor.
|
|
20
|
+
* Each modifier returns a new frozen object (immutable).
|
|
21
|
+
*/
|
|
22
|
+
export declare class ColumnBuilder<C extends ColumnDescriptor> {
|
|
23
|
+
readonly descriptor: C;
|
|
24
|
+
constructor(descriptor: C);
|
|
25
|
+
/** Mark this column as readonly (cannot be set in update patches) */
|
|
26
|
+
readonly(): ColumnBuilder<C & {
|
|
27
|
+
readonly isReadonly: true;
|
|
28
|
+
}>;
|
|
29
|
+
/** Mark this column as optional (nullable) */
|
|
30
|
+
optional(): ColumnBuilder<C & {
|
|
31
|
+
readonly isOptional: true;
|
|
32
|
+
}>;
|
|
33
|
+
/** Add a database index on this column */
|
|
34
|
+
indexed(): ColumnBuilder<C & {
|
|
35
|
+
readonly isIndexed: true;
|
|
36
|
+
}>;
|
|
37
|
+
/** Set a default value for this column */
|
|
38
|
+
default(value: unknown): ColumnBuilder<C>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Public schema builder API — the `m` object.
|
|
42
|
+
*/
|
|
43
|
+
export declare const m: {
|
|
44
|
+
/** Text (string) column */
|
|
45
|
+
text(columnName?: string): ColumnBuilder<TextColumn>;
|
|
46
|
+
/** Numeric column */
|
|
47
|
+
number(columnName?: string): ColumnBuilder<NumberColumn>;
|
|
48
|
+
/** Boolean column */
|
|
49
|
+
boolean(columnName?: string): ColumnBuilder<BooleanColumn>;
|
|
50
|
+
/** Date column (stored as epoch ms in the database) */
|
|
51
|
+
date(columnName?: string): ColumnBuilder<DateColumn>;
|
|
52
|
+
/** Belongs-to relation (many-to-one). Adds a foreign key column. */
|
|
53
|
+
belongsTo(relatedTable: string, opts: {
|
|
54
|
+
key: string;
|
|
55
|
+
}): BelongsToDescriptor;
|
|
56
|
+
/** Has-many relation (one-to-many). Query-only, no stored column. */
|
|
57
|
+
hasMany(relatedTable: string, opts: {
|
|
58
|
+
foreignKey: string;
|
|
59
|
+
}): HasManyDescriptor;
|
|
60
|
+
/**
|
|
61
|
+
* Define a model schema for the given table.
|
|
62
|
+
*
|
|
63
|
+
* Resolves all columns and relations, and returns a frozen ModelSchema
|
|
64
|
+
* that carries full type information.
|
|
65
|
+
*/
|
|
66
|
+
model<F extends Record<string, ColumnBuilder<ColumnDescriptor> | BelongsToDescriptor | HasManyDescriptor>>(table: string, fields: F): ModelSchema<{ [K in keyof F]: F[K] extends ColumnBuilder<infer D> ? D : F[K] extends FieldDescriptor ? F[K] : never; }>;
|
|
67
|
+
};
|
|
68
|
+
export { ColumnBuilder as _ColumnBuilder };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Schema builder — the `m` API.
|
|
4
|
+
*
|
|
5
|
+
* Provides a fluent, Zod-inspired builder for defining model schemas
|
|
6
|
+
* with full TypeScript inference.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const PostSchema = m.model('posts', {
|
|
10
|
+
* title: m.text(),
|
|
11
|
+
* body: m.text(),
|
|
12
|
+
* isPinned: m.boolean().default(false),
|
|
13
|
+
* createdAt: m.date('created_at').readonly(),
|
|
14
|
+
* author: m.belongsTo('users', { key: 'author_id' }),
|
|
15
|
+
* comments: m.hasMany('comments', { foreignKey: 'post_id' }),
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports._ColumnBuilder = exports.m = exports.ColumnBuilder = void 0;
|
|
20
|
+
// ─── Column Builder ────────────────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Fluent builder wrapping a ColumnDescriptor.
|
|
23
|
+
* Each modifier returns a new frozen object (immutable).
|
|
24
|
+
*/
|
|
25
|
+
class ColumnBuilder {
|
|
26
|
+
descriptor;
|
|
27
|
+
constructor(descriptor) {
|
|
28
|
+
this.descriptor = descriptor;
|
|
29
|
+
Object.freeze(this.descriptor);
|
|
30
|
+
}
|
|
31
|
+
/** Mark this column as readonly (cannot be set in update patches) */
|
|
32
|
+
readonly() {
|
|
33
|
+
return new ColumnBuilder({ ...this.descriptor, isReadonly: true });
|
|
34
|
+
}
|
|
35
|
+
/** Mark this column as optional (nullable) */
|
|
36
|
+
optional() {
|
|
37
|
+
return new ColumnBuilder({ ...this.descriptor, isOptional: true });
|
|
38
|
+
}
|
|
39
|
+
/** Add a database index on this column */
|
|
40
|
+
indexed() {
|
|
41
|
+
return new ColumnBuilder({ ...this.descriptor, isIndexed: true });
|
|
42
|
+
}
|
|
43
|
+
/** Set a default value for this column */
|
|
44
|
+
default(value) {
|
|
45
|
+
return new ColumnBuilder({ ...this.descriptor, defaultValue: value });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.ColumnBuilder = ColumnBuilder;
|
|
49
|
+
exports._ColumnBuilder = ColumnBuilder;
|
|
50
|
+
// Make ColumnBuilder look like a plain descriptor for schema inference
|
|
51
|
+
// by forwarding the key properties
|
|
52
|
+
function isColumnBuilder(v) {
|
|
53
|
+
return v instanceof ColumnBuilder;
|
|
54
|
+
}
|
|
55
|
+
// ─── Helper: resolve a field (ColumnBuilder | RelationDescriptor) ──────────
|
|
56
|
+
function resolveField(fieldName, raw) {
|
|
57
|
+
const desc = isColumnBuilder(raw) ? raw.descriptor : raw;
|
|
58
|
+
if ('type' in desc) {
|
|
59
|
+
const col = {
|
|
60
|
+
fieldName,
|
|
61
|
+
columnName: desc.columnName ?? fieldName,
|
|
62
|
+
type: desc.type,
|
|
63
|
+
isReadonly: desc.isReadonly,
|
|
64
|
+
isOptional: desc.isOptional,
|
|
65
|
+
isIndexed: desc.isIndexed,
|
|
66
|
+
defaultValue: desc.defaultValue,
|
|
67
|
+
};
|
|
68
|
+
return { column: col };
|
|
69
|
+
}
|
|
70
|
+
if ('kind' in desc) {
|
|
71
|
+
const rel = {
|
|
72
|
+
fieldName,
|
|
73
|
+
kind: desc.kind,
|
|
74
|
+
relatedTable: desc.relatedTable,
|
|
75
|
+
foreignKey: desc.foreignKey,
|
|
76
|
+
};
|
|
77
|
+
// belongs_to also implies a column for the foreign key
|
|
78
|
+
if (desc.kind === 'belongs_to') {
|
|
79
|
+
const col = {
|
|
80
|
+
fieldName,
|
|
81
|
+
columnName: desc.foreignKey,
|
|
82
|
+
type: 'text',
|
|
83
|
+
isReadonly: false,
|
|
84
|
+
isOptional: false,
|
|
85
|
+
isIndexed: true,
|
|
86
|
+
defaultValue: undefined,
|
|
87
|
+
};
|
|
88
|
+
return { column: col, relation: rel };
|
|
89
|
+
}
|
|
90
|
+
return { relation: rel };
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Unknown field descriptor for "${fieldName}"`);
|
|
93
|
+
}
|
|
94
|
+
// ─── The `m` namespace ─────────────────────────────────────────────────────
|
|
95
|
+
function makeColumn(type, columnName) {
|
|
96
|
+
return new ColumnBuilder({
|
|
97
|
+
type,
|
|
98
|
+
columnName,
|
|
99
|
+
isReadonly: false,
|
|
100
|
+
isOptional: false,
|
|
101
|
+
isIndexed: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Public schema builder API — the `m` object.
|
|
106
|
+
*/
|
|
107
|
+
exports.m = {
|
|
108
|
+
/** Text (string) column */
|
|
109
|
+
text(columnName) {
|
|
110
|
+
return makeColumn('text', columnName ?? null);
|
|
111
|
+
},
|
|
112
|
+
/** Numeric column */
|
|
113
|
+
number(columnName) {
|
|
114
|
+
return makeColumn('number', columnName ?? null);
|
|
115
|
+
},
|
|
116
|
+
/** Boolean column */
|
|
117
|
+
boolean(columnName) {
|
|
118
|
+
return makeColumn('boolean', columnName ?? null);
|
|
119
|
+
},
|
|
120
|
+
/** Date column (stored as epoch ms in the database) */
|
|
121
|
+
date(columnName) {
|
|
122
|
+
return makeColumn('date', columnName ?? null);
|
|
123
|
+
},
|
|
124
|
+
/** Belongs-to relation (many-to-one). Adds a foreign key column. */
|
|
125
|
+
belongsTo(relatedTable, opts) {
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
kind: 'belongs_to',
|
|
128
|
+
relatedTable,
|
|
129
|
+
foreignKey: opts.key,
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
/** Has-many relation (one-to-many). Query-only, no stored column. */
|
|
133
|
+
hasMany(relatedTable, opts) {
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
kind: 'has_many',
|
|
136
|
+
relatedTable,
|
|
137
|
+
foreignKey: opts.foreignKey,
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* Define a model schema for the given table.
|
|
142
|
+
*
|
|
143
|
+
* Resolves all columns and relations, and returns a frozen ModelSchema
|
|
144
|
+
* that carries full type information.
|
|
145
|
+
*/
|
|
146
|
+
model(table, fields) {
|
|
147
|
+
const columns = [];
|
|
148
|
+
const relations = [];
|
|
149
|
+
const resolvedFields = {};
|
|
150
|
+
for (const [name, raw] of Object.entries(fields)) {
|
|
151
|
+
const { column, relation } = resolveField(name, raw);
|
|
152
|
+
if (column)
|
|
153
|
+
columns.push(column);
|
|
154
|
+
if (relation)
|
|
155
|
+
relations.push(relation);
|
|
156
|
+
// Store the resolved descriptor
|
|
157
|
+
resolvedFields[name] = isColumnBuilder(raw) ? raw.descriptor : raw;
|
|
158
|
+
}
|
|
159
|
+
const schema = {
|
|
160
|
+
table,
|
|
161
|
+
fields: resolvedFields,
|
|
162
|
+
columns,
|
|
163
|
+
relations,
|
|
164
|
+
};
|
|
165
|
+
return Object.freeze(schema);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=builder.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { m, ColumnBuilder } from './builder';
|
|
2
|
+
export type { ColumnType, ColumnDescriptor, TextColumn, NumberColumn, BooleanColumn, DateColumn, BelongsToDescriptor, HasManyDescriptor, RelationDescriptor, FieldDescriptor, SchemaFields, ModelSchema, ResolvedColumn, ResolvedRelation, DatabaseSchema, TableSchema, TableColumnSchema, SyncStatus, SyncColumns, RawRecord, InferColumnType, InferField, InferCreatePatch, InferUpdatePatch, InferRecord, } from './types';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ColumnBuilder = exports.m = void 0;
|
|
4
|
+
var builder_1 = require("./builder");
|
|
5
|
+
Object.defineProperty(exports, "m", { enumerable: true, get: function () { return builder_1.m; } });
|
|
6
|
+
Object.defineProperty(exports, "ColumnBuilder", { enumerable: true, get: function () { return builder_1.ColumnBuilder; } });
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the schema system.
|
|
3
|
+
*
|
|
4
|
+
* The schema builder produces typed descriptors that the rest of the system
|
|
5
|
+
* uses to generate SQL, validate patches, and infer TypeScript types.
|
|
6
|
+
*/
|
|
7
|
+
export type ColumnType = 'text' | 'number' | 'boolean' | 'date';
|
|
8
|
+
export interface ColumnDescriptor {
|
|
9
|
+
readonly type: ColumnType;
|
|
10
|
+
readonly columnName: string | null;
|
|
11
|
+
readonly isReadonly: boolean;
|
|
12
|
+
readonly isOptional: boolean;
|
|
13
|
+
readonly isIndexed: boolean;
|
|
14
|
+
readonly defaultValue?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface TextColumn extends ColumnDescriptor {
|
|
17
|
+
readonly type: 'text';
|
|
18
|
+
}
|
|
19
|
+
export interface NumberColumn extends ColumnDescriptor {
|
|
20
|
+
readonly type: 'number';
|
|
21
|
+
}
|
|
22
|
+
export interface BooleanColumn extends ColumnDescriptor {
|
|
23
|
+
readonly type: 'boolean';
|
|
24
|
+
}
|
|
25
|
+
export interface DateColumn extends ColumnDescriptor {
|
|
26
|
+
readonly type: 'date';
|
|
27
|
+
}
|
|
28
|
+
export type RelationType = 'belongs_to' | 'has_many';
|
|
29
|
+
export interface BelongsToDescriptor {
|
|
30
|
+
readonly kind: 'belongs_to';
|
|
31
|
+
readonly relatedTable: string;
|
|
32
|
+
readonly foreignKey: string;
|
|
33
|
+
}
|
|
34
|
+
export interface HasManyDescriptor {
|
|
35
|
+
readonly kind: 'has_many';
|
|
36
|
+
readonly relatedTable: string;
|
|
37
|
+
readonly foreignKey: string;
|
|
38
|
+
}
|
|
39
|
+
export type RelationDescriptor = BelongsToDescriptor | HasManyDescriptor;
|
|
40
|
+
export type FieldDescriptor = ColumnDescriptor | RelationDescriptor;
|
|
41
|
+
/** The raw shape definition passed to `m.model()` */
|
|
42
|
+
export type SchemaFields = Record<string, FieldDescriptor>;
|
|
43
|
+
/** Compiled model schema with table name and resolved columns */
|
|
44
|
+
export interface ModelSchema<F extends SchemaFields = SchemaFields> {
|
|
45
|
+
readonly table: string;
|
|
46
|
+
readonly fields: F;
|
|
47
|
+
readonly columns: ResolvedColumn[];
|
|
48
|
+
readonly relations: ResolvedRelation[];
|
|
49
|
+
}
|
|
50
|
+
export interface ResolvedColumn {
|
|
51
|
+
readonly fieldName: string;
|
|
52
|
+
readonly columnName: string;
|
|
53
|
+
readonly type: ColumnType;
|
|
54
|
+
readonly isReadonly: boolean;
|
|
55
|
+
readonly isOptional: boolean;
|
|
56
|
+
readonly isIndexed: boolean;
|
|
57
|
+
readonly defaultValue?: unknown;
|
|
58
|
+
}
|
|
59
|
+
export interface ResolvedRelation {
|
|
60
|
+
readonly fieldName: string;
|
|
61
|
+
readonly kind: RelationType;
|
|
62
|
+
readonly relatedTable: string;
|
|
63
|
+
readonly foreignKey: string;
|
|
64
|
+
}
|
|
65
|
+
export interface DatabaseSchema {
|
|
66
|
+
readonly version: number;
|
|
67
|
+
readonly tables: TableSchema[];
|
|
68
|
+
}
|
|
69
|
+
export interface TableSchema {
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly columns: TableColumnSchema[];
|
|
72
|
+
}
|
|
73
|
+
export interface TableColumnSchema {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly type: ColumnType;
|
|
76
|
+
readonly isOptional: boolean;
|
|
77
|
+
readonly isIndexed: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** Infer the runtime TypeScript type from a ColumnDescriptor */
|
|
80
|
+
export type InferColumnType<C extends ColumnDescriptor> = C['type'] extends 'text' ? string : C['type'] extends 'number' ? number : C['type'] extends 'boolean' ? boolean : C['type'] extends 'date' ? Date : never;
|
|
81
|
+
/** For optional columns, make the type T | null */
|
|
82
|
+
type MaybeOptional<C extends ColumnDescriptor, T> = C['isOptional'] extends true ? T | null : T;
|
|
83
|
+
/** Infer column type respecting optionality */
|
|
84
|
+
export type InferField<C extends FieldDescriptor> = C extends ColumnDescriptor ? MaybeOptional<C, InferColumnType<C>> : C extends BelongsToDescriptor ? string : C extends HasManyDescriptor ? never : never;
|
|
85
|
+
/** The record shape inferred from schema fields (writable columns only) */
|
|
86
|
+
export type InferCreatePatch<F extends SchemaFields> = {
|
|
87
|
+
[K in keyof F as F[K] extends ColumnDescriptor ? F[K]['isReadonly'] extends true ? never : K : F[K] extends BelongsToDescriptor ? K : never]: F[K] extends ColumnDescriptor ? MaybeOptional<F[K], InferColumnType<F[K]>> : F[K] extends BelongsToDescriptor ? string : never;
|
|
88
|
+
};
|
|
89
|
+
/** The record shape for updates — all writable fields optional */
|
|
90
|
+
export type InferUpdatePatch<F extends SchemaFields> = Partial<InferCreatePatch<F>>;
|
|
91
|
+
/** Full record shape (all columns + belongs_to keys) */
|
|
92
|
+
export type InferRecord<F extends SchemaFields> = {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
} & {
|
|
95
|
+
readonly [K in keyof F]: InferField<F[K]>;
|
|
96
|
+
};
|
|
97
|
+
export type SyncStatus = 'synced' | 'created' | 'updated' | 'deleted';
|
|
98
|
+
/** Every persisted row carries sync metadata */
|
|
99
|
+
export interface SyncColumns {
|
|
100
|
+
readonly _status: SyncStatus;
|
|
101
|
+
readonly _changed: string;
|
|
102
|
+
}
|
|
103
|
+
/** Raw row as stored in the adapter (values are primitives) */
|
|
104
|
+
export interface RawRecord extends SyncColumns {
|
|
105
|
+
readonly id: string;
|
|
106
|
+
[column: string]: unknown;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Core type definitions for the schema system.
|
|
4
|
+
*
|
|
5
|
+
* The schema builder produces typed descriptors that the rest of the system
|
|
6
|
+
* uses to generate SQL, validate patches, and infer TypeScript types.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.performSync = void 0;
|
|
4
|
+
var sync_1 = require("./sync");
|
|
5
|
+
Object.defineProperty(exports, "performSync", { enumerable: true, get: function () { return sync_1.performSync; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync engine — pull/push protocol compatible with Watermelon backend sync.
|
|
3
|
+
*
|
|
4
|
+
* The sync cycle:
|
|
5
|
+
* 1. Push local changes to the server
|
|
6
|
+
* 2. Pull remote changes from the server
|
|
7
|
+
* 3. Apply remote changes locally (in a transaction)
|
|
8
|
+
* 4. Mark pushed records as synced
|
|
9
|
+
*
|
|
10
|
+
* This follows a "push-first" strategy to minimize conflicts:
|
|
11
|
+
* the server sees our changes before we pull theirs.
|
|
12
|
+
*/
|
|
13
|
+
import type { Database } from '../database/Database';
|
|
14
|
+
import type { SyncConfig } from './types';
|
|
15
|
+
export declare function performSync(db: Database, config: SyncConfig): Promise<void>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sync engine — pull/push protocol compatible with Watermelon backend sync.
|
|
4
|
+
*
|
|
5
|
+
* The sync cycle:
|
|
6
|
+
* 1. Push local changes to the server
|
|
7
|
+
* 2. Pull remote changes from the server
|
|
8
|
+
* 3. Apply remote changes locally (in a transaction)
|
|
9
|
+
* 4. Mark pushed records as synced
|
|
10
|
+
*
|
|
11
|
+
* This follows a "push-first" strategy to minimize conflicts:
|
|
12
|
+
* the server sees our changes before we pull theirs.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.performSync = performSync;
|
|
16
|
+
const utils_1 = require("../utils");
|
|
17
|
+
// ─── Last Pulled At Storage ──────────────────────────────────────────────
|
|
18
|
+
const LAST_PULLED_AT_KEY = 'pomegranate_last_pulled_at';
|
|
19
|
+
async function getLastPulledAt(db) {
|
|
20
|
+
try {
|
|
21
|
+
// Store in adapter metadata if available, else use in-memory
|
|
22
|
+
const raw = await db._adapter.findById('__pomegranate_metadata', LAST_PULLED_AT_KEY);
|
|
23
|
+
if (raw)
|
|
24
|
+
return Number(raw.value) || null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// metadata table might not have this record
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
async function setLastPulledAt(db, timestamp) {
|
|
32
|
+
try {
|
|
33
|
+
await db._adapter.batch([
|
|
34
|
+
{
|
|
35
|
+
type: 'create',
|
|
36
|
+
table: '__pomegranate_metadata',
|
|
37
|
+
rawRecord: { id: LAST_PULLED_AT_KEY, key: LAST_PULLED_AT_KEY, value: String(timestamp) },
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// If record exists, update it
|
|
43
|
+
try {
|
|
44
|
+
await db._adapter.update('__pomegranate_metadata', {
|
|
45
|
+
id: LAST_PULLED_AT_KEY,
|
|
46
|
+
key: LAST_PULLED_AT_KEY,
|
|
47
|
+
value: String(timestamp),
|
|
48
|
+
_status: 'synced',
|
|
49
|
+
_changed: '',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
utils_1.logger.warn('Could not persist lastPulledAt timestamp');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// ─── Sync Implementation ────────────────────────────────────────────────
|
|
58
|
+
async function performSync(db, config) {
|
|
59
|
+
const tables = config.tables ?? db.tables;
|
|
60
|
+
const lastPulledAt = await getLastPulledAt(db);
|
|
61
|
+
utils_1.logger.debug(`Sync starting. lastPulledAt: ${lastPulledAt}`);
|
|
62
|
+
// ── Step 1: Get local changes ──
|
|
63
|
+
const localChanges = await db._adapter.getLocalChanges(tables);
|
|
64
|
+
const hasLocalChanges = Object.values(localChanges).some((tc) => tc.created.length > 0 || tc.updated.length > 0 || tc.deleted.length > 0);
|
|
65
|
+
// Track which records were locally modified (needed for conflict detection after push)
|
|
66
|
+
const locallyModifiedIds = new Set();
|
|
67
|
+
const locallyModifiedRecords = new Map();
|
|
68
|
+
for (const [_table, tc] of Object.entries(localChanges)) {
|
|
69
|
+
for (const r of tc.updated) {
|
|
70
|
+
locallyModifiedIds.add(r.id);
|
|
71
|
+
locallyModifiedRecords.set(r.id, r);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ── Step 2: Push local changes (if any) ──
|
|
75
|
+
if (hasLocalChanges) {
|
|
76
|
+
utils_1.logger.debug('Pushing local changes...');
|
|
77
|
+
// Strip internal sync fields before pushing
|
|
78
|
+
const pushPayload = sanitizeForPush(localChanges);
|
|
79
|
+
await config.pushChanges({
|
|
80
|
+
changes: pushPayload,
|
|
81
|
+
lastPulledAt: lastPulledAt ?? 0,
|
|
82
|
+
});
|
|
83
|
+
// Mark all pushed records as synced
|
|
84
|
+
for (const table of tables) {
|
|
85
|
+
const tableChanges = localChanges[table];
|
|
86
|
+
if (!tableChanges)
|
|
87
|
+
continue;
|
|
88
|
+
const syncedIds = [
|
|
89
|
+
...tableChanges.created.map((r) => r.id),
|
|
90
|
+
...tableChanges.updated.map((r) => r.id),
|
|
91
|
+
];
|
|
92
|
+
if (syncedIds.length > 0) {
|
|
93
|
+
await db._adapter.markAsSynced(table, syncedIds);
|
|
94
|
+
}
|
|
95
|
+
// Permanently remove locally-deleted records that were pushed
|
|
96
|
+
if (tableChanges.deleted.length > 0) {
|
|
97
|
+
for (const id of tableChanges.deleted) {
|
|
98
|
+
await db._adapter.destroyPermanently(table, id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
utils_1.logger.debug('Push complete.');
|
|
103
|
+
}
|
|
104
|
+
// ── Step 3: Pull remote changes ──
|
|
105
|
+
utils_1.logger.debug('Pulling remote changes...');
|
|
106
|
+
const pullResult = await config.pullChanges({ lastPulledAt });
|
|
107
|
+
// ── Step 4: Apply remote changes ──
|
|
108
|
+
const remoteChanges = pullResult.changes;
|
|
109
|
+
const hasRemoteChanges = Object.values(remoteChanges).some((tc) => tc.created.length > 0 || tc.updated.length > 0 || tc.deleted.length > 0);
|
|
110
|
+
if (hasRemoteChanges) {
|
|
111
|
+
utils_1.logger.debug('Applying remote changes...');
|
|
112
|
+
// Handle conflicts: if a record was modified both locally and remotely
|
|
113
|
+
if (config.onConflict) {
|
|
114
|
+
await resolveConflicts(db, remoteChanges, config.onConflict, locallyModifiedIds, locallyModifiedRecords);
|
|
115
|
+
}
|
|
116
|
+
await db._adapter.applyRemoteChanges(remoteChanges);
|
|
117
|
+
// Clear caches for affected collections
|
|
118
|
+
for (const table of Object.keys(remoteChanges)) {
|
|
119
|
+
try {
|
|
120
|
+
const collection = db.collection(table);
|
|
121
|
+
collection._clearCache();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Table might not have a registered collection
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
utils_1.logger.debug('Remote changes applied.');
|
|
128
|
+
}
|
|
129
|
+
// ── Step 5: Update lastPulledAt ──
|
|
130
|
+
await setLastPulledAt(db, pullResult.timestamp);
|
|
131
|
+
utils_1.logger.debug(`Sync complete. New lastPulledAt: ${pullResult.timestamp}`);
|
|
132
|
+
}
|
|
133
|
+
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
134
|
+
/**
|
|
135
|
+
* Remove internal sync columns (_status, _changed) from records
|
|
136
|
+
* before sending to the server.
|
|
137
|
+
*/
|
|
138
|
+
function sanitizeForPush(changes) {
|
|
139
|
+
const sanitized = {};
|
|
140
|
+
for (const [table, tableChanges] of Object.entries(changes)) {
|
|
141
|
+
sanitized[table] = {
|
|
142
|
+
created: tableChanges.created.map(stripSyncColumns),
|
|
143
|
+
updated: tableChanges.updated.map(stripSyncColumns),
|
|
144
|
+
deleted: tableChanges.deleted,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return sanitized;
|
|
148
|
+
}
|
|
149
|
+
function stripSyncColumns(raw) {
|
|
150
|
+
const { _status, _changed, ...rest } = raw;
|
|
151
|
+
return { ...rest, _status: 'synced', _changed: '' };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve conflicts between local and remote changes.
|
|
155
|
+
* Uses locallyModifiedIds (collected before push) to detect records that
|
|
156
|
+
* were modified locally, since push has already marked them as synced.
|
|
157
|
+
*/
|
|
158
|
+
async function resolveConflicts(db, remoteChanges, onConflict, locallyModifiedIds, locallyModifiedRecords) {
|
|
159
|
+
for (const [table, tableChanges] of Object.entries(remoteChanges)) {
|
|
160
|
+
const resolvedUpdates = [];
|
|
161
|
+
for (const remoteRecord of tableChanges.updated) {
|
|
162
|
+
// Check if this record was locally modified (before push)
|
|
163
|
+
if (locallyModifiedIds.has(remoteRecord.id)) {
|
|
164
|
+
const localRecord = locallyModifiedRecords.get(remoteRecord.id) ??
|
|
165
|
+
(await db._adapter.findById(table, remoteRecord.id));
|
|
166
|
+
if (localRecord) {
|
|
167
|
+
// Conflict! Both modified locally and remotely
|
|
168
|
+
const resolved = onConflict(localRecord, remoteRecord);
|
|
169
|
+
resolvedUpdates.push({ ...resolved, _status: 'synced', _changed: '' });
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
resolvedUpdates.push(remoteRecord);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
resolvedUpdates.push(remoteRecord);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
tableChanges.updated = resolvedUpdates;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=sync.js.map
|