@chipmobilesdk/rn-local-db 0.2.0 → 0.2.1
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/CHANGELOG.md +122 -102
- package/README.md +728 -728
- package/package.json +59 -62
- package/skills/sdk-rn-local-db/SKILL.md +0 -1
- package/src/adapter/budgets.ts +67 -67
- package/src/adapter/collection.ts +303 -298
- package/src/adapter/deletion.ts +252 -252
- package/src/adapter/index.ts +64 -68
- package/src/adapter/lifecycle.ts +517 -520
- package/src/adapter/transaction.ts +71 -71
- package/src/artifacts/availability.ts +67 -67
- package/src/artifacts/cascade.ts +114 -114
- package/src/artifacts/paths.ts +29 -29
- package/src/artifacts/reconcile.ts +63 -63
- package/src/artifacts/store.ts +334 -345
- package/src/contract/adapter.ts +76 -78
- package/src/contract/attachment.ts +75 -78
- package/src/contract/backup.ts +28 -28
- package/src/contract/config.ts +54 -54
- package/src/contract/encryption.ts +34 -34
- package/src/contract/errors.ts +76 -76
- package/src/contract/migration.ts +42 -42
- package/src/contract/query.ts +81 -81
- package/src/contract/record.ts +46 -51
- package/src/contract/schema.ts +36 -36
- package/src/contract/scope.ts +36 -36
- package/src/contract/transaction.ts +52 -54
- package/src/crypto/chunked-cipher.ts +153 -150
- package/src/crypto/derive.ts +64 -64
- package/src/crypto/key-provider.ts +78 -78
- package/src/crypto/provider.ts +81 -81
- package/src/diagnostics/dev-log.ts +112 -108
- package/src/diagnostics/errors.ts +62 -66
- package/src/diagnostics/faults.ts +58 -64
- package/src/diagnostics/map-native.ts +108 -106
- package/src/diagnostics/redact.ts +137 -141
- package/src/index.ts +80 -110
- package/src/migration/executor.ts +132 -136
- package/src/migration/ledger.ts +95 -97
- package/src/migration/validate-chain.ts +65 -65
- package/src/platform/backup-android-verify.ts +88 -88
- package/src/platform/backup-ios.ts +48 -48
- package/src/platform/bytes.ts +144 -144
- package/src/platform/fs.ts +89 -89
- package/src/platform/location.ts +135 -135
- package/src/query/budgets.ts +54 -54
- package/src/query/compare.ts +91 -91
- package/src/query/cursor.ts +100 -100
- package/src/query/filter.ts +82 -82
- package/src/query/paginate.ts +51 -51
- package/src/query/sort.ts +55 -56
- package/src/query/validate.ts +171 -171
- package/src/schema/declare.ts +42 -45
- package/src/schema/validate-config.ts +158 -158
- package/src/scope/hash.ts +131 -134
- package/src/scope/normalize.ts +83 -90
- package/src/scope/validate.ts +83 -83
- package/src/sqlite/adapter.ts +26 -26
- package/src/sqlite/batch.ts +242 -242
- package/src/sqlite/catalog.ts +105 -108
- package/src/sqlite/connection.ts +227 -227
- package/src/sqlite/index-coverage.ts +54 -56
- package/src/sqlite/native.ts +83 -83
- package/src/sqlite/row-codec.ts +217 -221
- package/src/sqlite/sql-builder.ts +244 -244
- package/src/sqlite/transaction-queue.ts +87 -87
- package/src/sqlite/transactions.ts +33 -33
- package/src/sqlite/values.ts +24 -24
- package/src/testing/conformance/attachments.ts +138 -136
- package/src/testing/conformance/encryption.ts +150 -150
- package/src/testing/conformance/index.ts +161 -161
- package/src/testing/conformance/lifecycle.ts +99 -99
- package/src/testing/conformance/migrations.ts +177 -177
- package/src/testing/conformance/queries.ts +204 -167
- package/src/testing/conformance/records.ts +169 -169
- package/src/testing/conformance/scopes.ts +86 -88
- package/src/testing/conformance/transactions.ts +149 -149
- package/src/testing/faults.ts +21 -21
- package/src/testing/index.ts +17 -21
- package/src/testing/memory-adapter/collection.ts +214 -219
- package/src/testing/memory-adapter/index.ts +510 -492
- package/src/testing/memory-adapter/migrations.ts +117 -119
- package/src/testing/memory-adapter/reset.ts +50 -50
- package/src/testing/memory-adapter/store.ts +128 -134
package/src/contract/errors.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Diagnostic codes.
|
|
3
|
-
*
|
|
4
|
-
* Stable identifiers so an app can choose a response without parsing a
|
|
5
|
-
* message (FR-074, FR-082). Disk-full, filesystem-access, and corruption stay
|
|
6
|
-
* distinct rather than collapsing into a generic write failure (FR-076) —
|
|
7
|
-
* an app frees space for one, retries after unlock for another, and offers a
|
|
8
|
-
* reset for the third.
|
|
9
|
-
*
|
|
10
|
-
* Full table with recommended recovery per code:
|
|
11
|
-
* specs/008-rn-local-db-package/contracts/errors.md
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export const DiagnosticCodes = {
|
|
15
|
-
// Configuration and scope
|
|
16
|
-
CONFIG_INVALID: 'CONFIG_INVALID',
|
|
17
|
-
INVALID_SCOPE: 'INVALID_SCOPE',
|
|
18
|
-
MIGRATION_CONFIG_INVALID: 'MIGRATION_CONFIG_INVALID',
|
|
19
|
-
|
|
20
|
-
// Open and lifecycle
|
|
21
|
-
OPEN_FAILED: 'OPEN_FAILED',
|
|
22
|
-
CLOSED_HANDLE: 'CLOSED_HANDLE',
|
|
23
|
-
SCOPE_DELETED: 'SCOPE_DELETED',
|
|
24
|
-
CORRUPTION: 'CORRUPTION',
|
|
25
|
-
STORAGE_ACCESS: 'STORAGE_ACCESS',
|
|
26
|
-
|
|
27
|
-
// Migration
|
|
28
|
-
MIGRATION_FAILED: 'MIGRATION_FAILED',
|
|
29
|
-
SCHEMA_DOWNGRADE: 'SCHEMA_DOWNGRADE',
|
|
30
|
-
|
|
31
|
-
// Records and writes
|
|
32
|
-
RECORD_NOT_FOUND: 'RECORD_NOT_FOUND',
|
|
33
|
-
RECORD_CONFLICT: 'RECORD_CONFLICT',
|
|
34
|
-
RECORD_TOO_LARGE: 'RECORD_TOO_LARGE',
|
|
35
|
-
BATCH_TOO_LARGE: 'BATCH_TOO_LARGE',
|
|
36
|
-
DISK_FULL: 'DISK_FULL',
|
|
37
|
-
WRITE_FAILED: 'WRITE_FAILED',
|
|
38
|
-
|
|
39
|
-
// Query
|
|
40
|
-
QUERY_INVALID: 'QUERY_INVALID',
|
|
41
|
-
PAGE_SIZE_INVALID: 'PAGE_SIZE_INVALID',
|
|
42
|
-
CURSOR_INVALID: 'CURSOR_INVALID',
|
|
43
|
-
|
|
44
|
-
// Transaction
|
|
45
|
-
TRANSACTION_FAILED: 'TRANSACTION_FAILED',
|
|
46
|
-
TRANSACTION_STATE: 'TRANSACTION_STATE',
|
|
47
|
-
|
|
48
|
-
// Attachments
|
|
49
|
-
ATTACHMENT_MISSING: 'ATTACHMENT_MISSING',
|
|
50
|
-
ATTACHMENT_OUT_OF_BOUNDS: 'ATTACHMENT_OUT_OF_BOUNDS',
|
|
51
|
-
ATTACHMENT_WRITE_FAILED: 'ATTACHMENT_WRITE_FAILED',
|
|
52
|
-
ATTACHMENT_UNDECRYPTABLE: 'ATTACHMENT_UNDECRYPTABLE',
|
|
53
|
-
|
|
54
|
-
// Encryption
|
|
55
|
-
KEY_INVALID: 'KEY_INVALID',
|
|
56
|
-
KEY_PROVIDER_FAILED: 'KEY_PROVIDER_FAILED',
|
|
57
|
-
ENCRYPTION_MISMATCH: 'ENCRYPTION_MISMATCH',
|
|
58
|
-
ENCRYPTION_UNAVAILABLE: 'ENCRYPTION_UNAVAILABLE',
|
|
59
|
-
} as const;
|
|
60
|
-
|
|
61
|
-
export type DiagnosticCode = (typeof DiagnosticCodes)[keyof typeof DiagnosticCodes];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Codes worth retrying. `KEY_PROVIDER_FAILED` is here because a locked device
|
|
65
|
-
* is an expected condition, not a bug (FR-102).
|
|
66
|
-
*/
|
|
67
|
-
export const RETRYABLE_CODES: ReadonlySet<DiagnosticCode> = new Set<DiagnosticCode>([
|
|
68
|
-
DiagnosticCodes.OPEN_FAILED,
|
|
69
|
-
DiagnosticCodes.DISK_FULL,
|
|
70
|
-
DiagnosticCodes.WRITE_FAILED,
|
|
71
|
-
DiagnosticCodes.STORAGE_ACCESS,
|
|
72
|
-
DiagnosticCodes.TRANSACTION_FAILED,
|
|
73
|
-
DiagnosticCodes.MIGRATION_FAILED,
|
|
74
|
-
DiagnosticCodes.ATTACHMENT_WRITE_FAILED,
|
|
75
|
-
DiagnosticCodes.KEY_PROVIDER_FAILED,
|
|
76
|
-
]);
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic codes.
|
|
3
|
+
*
|
|
4
|
+
* Stable identifiers so an app can choose a response without parsing a
|
|
5
|
+
* message (FR-074, FR-082). Disk-full, filesystem-access, and corruption stay
|
|
6
|
+
* distinct rather than collapsing into a generic write failure (FR-076) —
|
|
7
|
+
* an app frees space for one, retries after unlock for another, and offers a
|
|
8
|
+
* reset for the third.
|
|
9
|
+
*
|
|
10
|
+
* Full table with recommended recovery per code:
|
|
11
|
+
* specs/008-rn-local-db-package/contracts/errors.md
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const DiagnosticCodes = {
|
|
15
|
+
// Configuration and scope
|
|
16
|
+
CONFIG_INVALID: 'CONFIG_INVALID',
|
|
17
|
+
INVALID_SCOPE: 'INVALID_SCOPE',
|
|
18
|
+
MIGRATION_CONFIG_INVALID: 'MIGRATION_CONFIG_INVALID',
|
|
19
|
+
|
|
20
|
+
// Open and lifecycle
|
|
21
|
+
OPEN_FAILED: 'OPEN_FAILED',
|
|
22
|
+
CLOSED_HANDLE: 'CLOSED_HANDLE',
|
|
23
|
+
SCOPE_DELETED: 'SCOPE_DELETED',
|
|
24
|
+
CORRUPTION: 'CORRUPTION',
|
|
25
|
+
STORAGE_ACCESS: 'STORAGE_ACCESS',
|
|
26
|
+
|
|
27
|
+
// Migration
|
|
28
|
+
MIGRATION_FAILED: 'MIGRATION_FAILED',
|
|
29
|
+
SCHEMA_DOWNGRADE: 'SCHEMA_DOWNGRADE',
|
|
30
|
+
|
|
31
|
+
// Records and writes
|
|
32
|
+
RECORD_NOT_FOUND: 'RECORD_NOT_FOUND',
|
|
33
|
+
RECORD_CONFLICT: 'RECORD_CONFLICT',
|
|
34
|
+
RECORD_TOO_LARGE: 'RECORD_TOO_LARGE',
|
|
35
|
+
BATCH_TOO_LARGE: 'BATCH_TOO_LARGE',
|
|
36
|
+
DISK_FULL: 'DISK_FULL',
|
|
37
|
+
WRITE_FAILED: 'WRITE_FAILED',
|
|
38
|
+
|
|
39
|
+
// Query
|
|
40
|
+
QUERY_INVALID: 'QUERY_INVALID',
|
|
41
|
+
PAGE_SIZE_INVALID: 'PAGE_SIZE_INVALID',
|
|
42
|
+
CURSOR_INVALID: 'CURSOR_INVALID',
|
|
43
|
+
|
|
44
|
+
// Transaction
|
|
45
|
+
TRANSACTION_FAILED: 'TRANSACTION_FAILED',
|
|
46
|
+
TRANSACTION_STATE: 'TRANSACTION_STATE',
|
|
47
|
+
|
|
48
|
+
// Attachments
|
|
49
|
+
ATTACHMENT_MISSING: 'ATTACHMENT_MISSING',
|
|
50
|
+
ATTACHMENT_OUT_OF_BOUNDS: 'ATTACHMENT_OUT_OF_BOUNDS',
|
|
51
|
+
ATTACHMENT_WRITE_FAILED: 'ATTACHMENT_WRITE_FAILED',
|
|
52
|
+
ATTACHMENT_UNDECRYPTABLE: 'ATTACHMENT_UNDECRYPTABLE',
|
|
53
|
+
|
|
54
|
+
// Encryption
|
|
55
|
+
KEY_INVALID: 'KEY_INVALID',
|
|
56
|
+
KEY_PROVIDER_FAILED: 'KEY_PROVIDER_FAILED',
|
|
57
|
+
ENCRYPTION_MISMATCH: 'ENCRYPTION_MISMATCH',
|
|
58
|
+
ENCRYPTION_UNAVAILABLE: 'ENCRYPTION_UNAVAILABLE',
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export type DiagnosticCode = (typeof DiagnosticCodes)[keyof typeof DiagnosticCodes];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Codes worth retrying. `KEY_PROVIDER_FAILED` is here because a locked device
|
|
65
|
+
* is an expected condition, not a bug (FR-102).
|
|
66
|
+
*/
|
|
67
|
+
export const RETRYABLE_CODES: ReadonlySet<DiagnosticCode> = new Set<DiagnosticCode>([
|
|
68
|
+
DiagnosticCodes.OPEN_FAILED,
|
|
69
|
+
DiagnosticCodes.DISK_FULL,
|
|
70
|
+
DiagnosticCodes.WRITE_FAILED,
|
|
71
|
+
DiagnosticCodes.STORAGE_ACCESS,
|
|
72
|
+
DiagnosticCodes.TRANSACTION_FAILED,
|
|
73
|
+
DiagnosticCodes.MIGRATION_FAILED,
|
|
74
|
+
DiagnosticCodes.ATTACHMENT_WRITE_FAILED,
|
|
75
|
+
DiagnosticCodes.KEY_PROVIDER_FAILED,
|
|
76
|
+
]);
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Versioned migrations.
|
|
3
|
-
*
|
|
4
|
-
* The package never writes a migration and never alters domain data except
|
|
5
|
-
* through one the domain supplied (FR-056).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Transaction } from './transaction';
|
|
9
|
-
|
|
10
|
-
/** Low-level escape hatch for schema work a migration needs. */
|
|
11
|
-
// Attachments are not part of a migration: a migration reshapes records, and
|
|
12
|
-
// files written inside one would survive its rollback with nothing to own them.
|
|
13
|
-
export type MigrationContext = Omit<Transaction, 'attachments'> & {
|
|
14
|
-
/** Execute a raw statement against the scope's database. */
|
|
15
|
-
execute(sql: string, params?: unknown[]): Promise<void>;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type Migration = {
|
|
19
|
-
/** Unique, contiguous, and reaching the declared schemaVersion (FR-053). */
|
|
20
|
-
version: number;
|
|
21
|
-
migrate(context: MigrationContext): Promise<void>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type MigrationOutcome = 'applied' | 'failed';
|
|
25
|
-
|
|
26
|
-
export type MigrationLedgerEntry = {
|
|
27
|
-
version: number;
|
|
28
|
-
attemptedAt: number;
|
|
29
|
-
outcome: MigrationOutcome;
|
|
30
|
-
/** A diagnostic code only — never a message, which could carry payload. */
|
|
31
|
-
failureCode?: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Attached to a MIGRATION_FAILED error so an app can tell a transient failure
|
|
36
|
-
* from a permanent one and choose retry, reset, or asking the user (FR-057c).
|
|
37
|
-
*/
|
|
38
|
-
export type MigrationFailureDetail = {
|
|
39
|
-
recordedVersion: number;
|
|
40
|
-
failingVersion: number;
|
|
41
|
-
outcome: MigrationOutcome;
|
|
42
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Versioned migrations.
|
|
3
|
+
*
|
|
4
|
+
* The package never writes a migration and never alters domain data except
|
|
5
|
+
* through one the domain supplied (FR-056).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Transaction } from './transaction';
|
|
9
|
+
|
|
10
|
+
/** Low-level escape hatch for schema work a migration needs. */
|
|
11
|
+
// Attachments are not part of a migration: a migration reshapes records, and
|
|
12
|
+
// files written inside one would survive its rollback with nothing to own them.
|
|
13
|
+
export type MigrationContext = Omit<Transaction, 'attachments'> & {
|
|
14
|
+
/** Execute a raw statement against the scope's database. */
|
|
15
|
+
execute(sql: string, params?: unknown[]): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type Migration = {
|
|
19
|
+
/** Unique, contiguous, and reaching the declared schemaVersion (FR-053). */
|
|
20
|
+
version: number;
|
|
21
|
+
migrate(context: MigrationContext): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type MigrationOutcome = 'applied' | 'failed';
|
|
25
|
+
|
|
26
|
+
export type MigrationLedgerEntry = {
|
|
27
|
+
version: number;
|
|
28
|
+
attemptedAt: number;
|
|
29
|
+
outcome: MigrationOutcome;
|
|
30
|
+
/** A diagnostic code only — never a message, which could carry payload. */
|
|
31
|
+
failureCode?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attached to a MIGRATION_FAILED error so an app can tell a transient failure
|
|
36
|
+
* from a permanent one and choose retry, reset, or asking the user (FR-057c).
|
|
37
|
+
*/
|
|
38
|
+
export type MigrationFailureDetail = {
|
|
39
|
+
recordedVersion: number;
|
|
40
|
+
failingVersion: number;
|
|
41
|
+
outcome: MigrationOutcome;
|
|
42
|
+
};
|
package/src/contract/query.ts
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query contract.
|
|
3
|
-
*
|
|
4
|
-
* A query carries no scope, because it cannot address one — scope is bound to
|
|
5
|
-
* the handle and immutable (FR-008a). That absence is the isolation
|
|
6
|
-
* guarantee, not a convention.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { StorableRecordData, StoredRecord } from './record';
|
|
10
|
-
|
|
11
|
-
/** Values a filter may compare against. */
|
|
12
|
-
export type FilterValue = string | number | boolean | null;
|
|
13
|
-
|
|
14
|
-
export type ComparisonOperator =
|
|
15
|
-
| 'eq'
|
|
16
|
-
| 'ne'
|
|
17
|
-
| 'lt'
|
|
18
|
-
| 'lte'
|
|
19
|
-
| 'gt'
|
|
20
|
-
| 'gte'
|
|
21
|
-
| 'in'
|
|
22
|
-
| 'nin'
|
|
23
|
-
| 'between'
|
|
24
|
-
| 'isNull'
|
|
25
|
-
| 'isNotNull'
|
|
26
|
-
| 'exists';
|
|
27
|
-
|
|
28
|
-
export type ComparisonNode =
|
|
29
|
-
| { op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'; field: string; value: FilterValue }
|
|
30
|
-
| { op: 'in' | 'nin'; field: string; value: FilterValue[] }
|
|
31
|
-
| { op: 'between'; field: string; value: [FilterValue, FilterValue] }
|
|
32
|
-
| { op: 'isNull' | 'isNotNull'; field: string }
|
|
33
|
-
/** Distinguishes an absent field from one holding null (FR-019). */
|
|
34
|
-
| { op: 'exists'; field: string; value: boolean };
|
|
35
|
-
|
|
36
|
-
export type LogicalNode =
|
|
37
|
-
| { op: 'and'; nodes: FilterNode[] }
|
|
38
|
-
| { op: 'or'; nodes: FilterNode[] }
|
|
39
|
-
| { op: 'not'; node: FilterNode };
|
|
40
|
-
|
|
41
|
-
export type FilterNode = ComparisonNode | LogicalNode;
|
|
42
|
-
|
|
43
|
-
export type SortDirection = 'asc' | 'desc';
|
|
44
|
-
|
|
45
|
-
export type SortKey = {
|
|
46
|
-
field: string;
|
|
47
|
-
direction?: SortDirection;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type PageRequest = {
|
|
51
|
-
/** 1..500 (FR-036, FR-088). */
|
|
52
|
-
size: number;
|
|
53
|
-
/** Opaque keyset cursor from a previous page. */
|
|
54
|
-
cursor?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type Query = {
|
|
58
|
-
filter?: FilterNode;
|
|
59
|
-
sort?: SortKey[];
|
|
60
|
-
page?: PageRequest;
|
|
61
|
-
/** Soft-deleted records are excluded unless this is true (FR-021). */
|
|
62
|
-
includeSoftDeleted?: boolean;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export type ResultPage<T extends StorableRecordData = StorableRecordData> = {
|
|
66
|
-
records: StoredRecord<T>[];
|
|
67
|
-
/** Absent when there is no further page. */
|
|
68
|
-
cursor?: string;
|
|
69
|
-
hasMore: boolean;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Development-only answer to "is this query covered by an index?" (FR-037).
|
|
74
|
-
* Backed by EXPLAIN QUERY PLAN on the SQLite adapter.
|
|
75
|
-
*/
|
|
76
|
-
export type IndexCoverageReport = {
|
|
77
|
-
collection: string;
|
|
78
|
-
usesIndex: boolean;
|
|
79
|
-
indexName?: string;
|
|
80
|
-
detail: string;
|
|
81
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Query contract.
|
|
3
|
+
*
|
|
4
|
+
* A query carries no scope, because it cannot address one — scope is bound to
|
|
5
|
+
* the handle and immutable (FR-008a). That absence is the isolation
|
|
6
|
+
* guarantee, not a convention.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { StorableRecordData, StoredRecord } from './record';
|
|
10
|
+
|
|
11
|
+
/** Values a filter may compare against. */
|
|
12
|
+
export type FilterValue = string | number | boolean | null;
|
|
13
|
+
|
|
14
|
+
export type ComparisonOperator =
|
|
15
|
+
| 'eq'
|
|
16
|
+
| 'ne'
|
|
17
|
+
| 'lt'
|
|
18
|
+
| 'lte'
|
|
19
|
+
| 'gt'
|
|
20
|
+
| 'gte'
|
|
21
|
+
| 'in'
|
|
22
|
+
| 'nin'
|
|
23
|
+
| 'between'
|
|
24
|
+
| 'isNull'
|
|
25
|
+
| 'isNotNull'
|
|
26
|
+
| 'exists';
|
|
27
|
+
|
|
28
|
+
export type ComparisonNode =
|
|
29
|
+
| { op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'; field: string; value: FilterValue }
|
|
30
|
+
| { op: 'in' | 'nin'; field: string; value: FilterValue[] }
|
|
31
|
+
| { op: 'between'; field: string; value: [FilterValue, FilterValue] }
|
|
32
|
+
| { op: 'isNull' | 'isNotNull'; field: string }
|
|
33
|
+
/** Distinguishes an absent field from one holding null (FR-019). */
|
|
34
|
+
| { op: 'exists'; field: string; value: boolean };
|
|
35
|
+
|
|
36
|
+
export type LogicalNode =
|
|
37
|
+
| { op: 'and'; nodes: FilterNode[] }
|
|
38
|
+
| { op: 'or'; nodes: FilterNode[] }
|
|
39
|
+
| { op: 'not'; node: FilterNode };
|
|
40
|
+
|
|
41
|
+
export type FilterNode = ComparisonNode | LogicalNode;
|
|
42
|
+
|
|
43
|
+
export type SortDirection = 'asc' | 'desc';
|
|
44
|
+
|
|
45
|
+
export type SortKey = {
|
|
46
|
+
field: string;
|
|
47
|
+
direction?: SortDirection;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type PageRequest = {
|
|
51
|
+
/** 1..500 (FR-036, FR-088). */
|
|
52
|
+
size: number;
|
|
53
|
+
/** Opaque keyset cursor from a previous page. */
|
|
54
|
+
cursor?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type Query = {
|
|
58
|
+
filter?: FilterNode;
|
|
59
|
+
sort?: SortKey[];
|
|
60
|
+
page?: PageRequest;
|
|
61
|
+
/** Soft-deleted records are excluded unless this is true (FR-021). */
|
|
62
|
+
includeSoftDeleted?: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type ResultPage<T extends StorableRecordData = StorableRecordData> = {
|
|
66
|
+
records: StoredRecord<T>[];
|
|
67
|
+
/** Absent when there is no further page. */
|
|
68
|
+
cursor?: string;
|
|
69
|
+
hasMore: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Development-only answer to "is this query covered by an index?" (FR-037).
|
|
74
|
+
* Backed by EXPLAIN QUERY PLAN on the SQLite adapter.
|
|
75
|
+
*/
|
|
76
|
+
export type IndexCoverageReport = {
|
|
77
|
+
collection: string;
|
|
78
|
+
usesIndex: boolean;
|
|
79
|
+
indexName?: string;
|
|
80
|
+
detail: string;
|
|
81
|
+
};
|
package/src/contract/record.ts
CHANGED
|
@@ -1,51 +1,46 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Records and the four value conditions.
|
|
3
|
-
*
|
|
4
|
-
* The distinction FR-019 requires — record absent, field explicitly null,
|
|
5
|
-
* field absent, record soft-deleted — is preserved because reads reconstruct
|
|
6
|
-
* from the stored payload alone and never from projected columns. See
|
|
7
|
-
* research.md R6.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { DeletionOutcome } from './attachment';
|
|
11
|
-
|
|
12
|
-
/** Anything the package will store and hand back unchanged. */
|
|
13
|
-
export type StorableValue =
|
|
14
|
-
| string
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export type
|
|
24
|
-
id: string;
|
|
25
|
-
data: T;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* transaction the files are removed once it commits, so `removedArtifacts`
|
|
48
|
-
* lists what was scheduled. Added in 0.2.0.
|
|
49
|
-
*/
|
|
50
|
-
cascade?: DeletionOutcome;
|
|
51
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Records and the four value conditions.
|
|
3
|
+
*
|
|
4
|
+
* The distinction FR-019 requires — record absent, field explicitly null,
|
|
5
|
+
* field absent, record soft-deleted — is preserved because reads reconstruct
|
|
6
|
+
* from the stored payload alone and never from projected columns. See
|
|
7
|
+
* research.md R6.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DeletionOutcome } from './attachment';
|
|
11
|
+
|
|
12
|
+
/** Anything the package will store and hand back unchanged. */
|
|
13
|
+
export type StorableValue =
|
|
14
|
+
string | number | boolean | null | StorableValue[] | { [key: string]: StorableValue };
|
|
15
|
+
|
|
16
|
+
export type StorableRecordData = { [key: string]: StorableValue };
|
|
17
|
+
|
|
18
|
+
export type RecordInput<T extends StorableRecordData = StorableRecordData> = {
|
|
19
|
+
id: string;
|
|
20
|
+
data: T;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type StoredRecord<T extends StorableRecordData = StorableRecordData> = {
|
|
24
|
+
id: string;
|
|
25
|
+
data: T;
|
|
26
|
+
/** Present only when the collection declares timestamps (FR-020). */
|
|
27
|
+
createdAt?: number;
|
|
28
|
+
updatedAt?: number;
|
|
29
|
+
/** Present only when soft-deleted (FR-021). */
|
|
30
|
+
deletedAt?: number | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type WriteOperation = 'created' | 'updated' | 'deleted' | 'restored';
|
|
34
|
+
|
|
35
|
+
/** An upsert reports which it actually was (FR-026). */
|
|
36
|
+
export type WriteOutcome = {
|
|
37
|
+
operation: WriteOperation;
|
|
38
|
+
id: string;
|
|
39
|
+
/**
|
|
40
|
+
* Present on a delete when `cascadeAttachments` is on: which attachment files
|
|
41
|
+
* went with the record and which could not be removed (FR-071). Inside a
|
|
42
|
+
* transaction the files are removed once it commits, so `removedArtifacts`
|
|
43
|
+
* lists what was scheduled. Added in 0.2.0.
|
|
44
|
+
*/
|
|
45
|
+
cascade?: DeletionOutcome;
|
|
46
|
+
};
|
package/src/contract/schema.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collection schema: structure only.
|
|
3
|
-
*
|
|
4
|
-
* Declaring a field makes it queryable and projectable. It does NOT restrict
|
|
5
|
-
* what a record may contain — undeclared fields are stored and returned
|
|
6
|
-
* faithfully, they are simply not filterable or sortable (FR-017, FR-034).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export type FieldType = 'string' | 'number' | 'boolean' | 'timestamp';
|
|
10
|
-
|
|
11
|
-
export type FieldDeclaration = {
|
|
12
|
-
name: string;
|
|
13
|
-
type: FieldType;
|
|
14
|
-
/** Whether the projected column tolerates SQL NULL. Purely a query concern. */
|
|
15
|
-
nullable?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type IndexDeclaration = {
|
|
19
|
-
name: string;
|
|
20
|
-
/** Must all reference declared fields (FR-006). */
|
|
21
|
-
fields: string[];
|
|
22
|
-
unique?: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type CollectionSchema = {
|
|
26
|
-
name: string;
|
|
27
|
-
fields: FieldDeclaration[];
|
|
28
|
-
indexes?: IndexDeclaration[];
|
|
29
|
-
/**
|
|
30
|
-
* Opt-in per collection, off by default. When on, deleted rows are
|
|
31
|
-
* excluded from queries unless explicitly requested (FR-021).
|
|
32
|
-
*/
|
|
33
|
-
softDelete?: boolean;
|
|
34
|
-
/** When on, the package maintains createdAt / updatedAt (FR-020). */
|
|
35
|
-
timestamps?: boolean;
|
|
36
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Collection schema: structure only.
|
|
3
|
+
*
|
|
4
|
+
* Declaring a field makes it queryable and projectable. It does NOT restrict
|
|
5
|
+
* what a record may contain — undeclared fields are stored and returned
|
|
6
|
+
* faithfully, they are simply not filterable or sortable (FR-017, FR-034).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type FieldType = 'string' | 'number' | 'boolean' | 'timestamp';
|
|
10
|
+
|
|
11
|
+
export type FieldDeclaration = {
|
|
12
|
+
name: string;
|
|
13
|
+
type: FieldType;
|
|
14
|
+
/** Whether the projected column tolerates SQL NULL. Purely a query concern. */
|
|
15
|
+
nullable?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type IndexDeclaration = {
|
|
19
|
+
name: string;
|
|
20
|
+
/** Must all reference declared fields (FR-006). */
|
|
21
|
+
fields: string[];
|
|
22
|
+
unique?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type CollectionSchema = {
|
|
26
|
+
name: string;
|
|
27
|
+
fields: FieldDeclaration[];
|
|
28
|
+
indexes?: IndexDeclaration[];
|
|
29
|
+
/**
|
|
30
|
+
* Opt-in per collection, off by default. When on, deleted rows are
|
|
31
|
+
* excluded from queries unless explicitly requested (FR-021).
|
|
32
|
+
*/
|
|
33
|
+
softDelete?: boolean;
|
|
34
|
+
/** When on, the package maintains createdAt / updatedAt (FR-020). */
|
|
35
|
+
timestamps?: boolean;
|
|
36
|
+
};
|
package/src/contract/scope.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage scope: the isolation boundary for all data.
|
|
3
|
-
*
|
|
4
|
-
* Components are opaque values the consuming app supplies. The package
|
|
5
|
-
* composes, normalizes, and validates them but never interprets them, and
|
|
6
|
-
* never invents one of its own (FR-008, FR-009).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export type ScopeKind = 'guest' | 'identity' | 'tenant' | 'workspace' | 'custom';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* What an app supplies to name a scope.
|
|
13
|
-
*
|
|
14
|
-
* Every component is treated as sensitive (FR-014): none of these values
|
|
15
|
-
* reaches disk, a log, or an error. Only the SHA-256 digest of the canonical
|
|
16
|
-
* form does.
|
|
17
|
-
*/
|
|
18
|
-
export type ScopeDescriptor = {
|
|
19
|
-
kind: ScopeKind;
|
|
20
|
-
/** Required for every kind except `guest`. */
|
|
21
|
-
id?: string;
|
|
22
|
-
tenant?: string;
|
|
23
|
-
app?: string;
|
|
24
|
-
environment?: string;
|
|
25
|
-
namespace?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/** The resolved scope: a hex digest, plus the kind it came from. */
|
|
29
|
-
export type ResolvedScope = {
|
|
30
|
-
/** SHA-256 hex of the canonical descriptor. Names the scope's directory. */
|
|
31
|
-
key: string;
|
|
32
|
-
kind: ScopeKind;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Where a scope sits in its lifecycle. Persisted in the catalog (FR-063). */
|
|
36
|
-
export type ScopeDeletionState = 'active' | 'deleting';
|
|
1
|
+
/**
|
|
2
|
+
* Storage scope: the isolation boundary for all data.
|
|
3
|
+
*
|
|
4
|
+
* Components are opaque values the consuming app supplies. The package
|
|
5
|
+
* composes, normalizes, and validates them but never interprets them, and
|
|
6
|
+
* never invents one of its own (FR-008, FR-009).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type ScopeKind = 'guest' | 'identity' | 'tenant' | 'workspace' | 'custom';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What an app supplies to name a scope.
|
|
13
|
+
*
|
|
14
|
+
* Every component is treated as sensitive (FR-014): none of these values
|
|
15
|
+
* reaches disk, a log, or an error. Only the SHA-256 digest of the canonical
|
|
16
|
+
* form does.
|
|
17
|
+
*/
|
|
18
|
+
export type ScopeDescriptor = {
|
|
19
|
+
kind: ScopeKind;
|
|
20
|
+
/** Required for every kind except `guest`. */
|
|
21
|
+
id?: string;
|
|
22
|
+
tenant?: string;
|
|
23
|
+
app?: string;
|
|
24
|
+
environment?: string;
|
|
25
|
+
namespace?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** The resolved scope: a hex digest, plus the kind it came from. */
|
|
29
|
+
export type ResolvedScope = {
|
|
30
|
+
/** SHA-256 hex of the canonical descriptor. Names the scope's directory. */
|
|
31
|
+
key: string;
|
|
32
|
+
kind: ScopeKind;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Where a scope sits in its lifecycle. Persisted in the catalog (FR-063). */
|
|
36
|
+
export type ScopeDeletionState = 'active' | 'deleting';
|