@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +122 -102
  2. package/README.md +728 -728
  3. package/package.json +59 -62
  4. package/skills/sdk-rn-local-db/SKILL.md +0 -1
  5. package/src/adapter/budgets.ts +67 -67
  6. package/src/adapter/collection.ts +303 -298
  7. package/src/adapter/deletion.ts +252 -252
  8. package/src/adapter/index.ts +64 -68
  9. package/src/adapter/lifecycle.ts +517 -520
  10. package/src/adapter/transaction.ts +71 -71
  11. package/src/artifacts/availability.ts +67 -67
  12. package/src/artifacts/cascade.ts +114 -114
  13. package/src/artifacts/paths.ts +29 -29
  14. package/src/artifacts/reconcile.ts +63 -63
  15. package/src/artifacts/store.ts +334 -345
  16. package/src/contract/adapter.ts +76 -78
  17. package/src/contract/attachment.ts +75 -78
  18. package/src/contract/backup.ts +28 -28
  19. package/src/contract/config.ts +54 -54
  20. package/src/contract/encryption.ts +34 -34
  21. package/src/contract/errors.ts +76 -76
  22. package/src/contract/migration.ts +42 -42
  23. package/src/contract/query.ts +81 -81
  24. package/src/contract/record.ts +46 -51
  25. package/src/contract/schema.ts +36 -36
  26. package/src/contract/scope.ts +36 -36
  27. package/src/contract/transaction.ts +52 -54
  28. package/src/crypto/chunked-cipher.ts +153 -150
  29. package/src/crypto/derive.ts +64 -64
  30. package/src/crypto/key-provider.ts +78 -78
  31. package/src/crypto/provider.ts +81 -81
  32. package/src/diagnostics/dev-log.ts +112 -108
  33. package/src/diagnostics/errors.ts +62 -66
  34. package/src/diagnostics/faults.ts +58 -64
  35. package/src/diagnostics/map-native.ts +108 -106
  36. package/src/diagnostics/redact.ts +137 -141
  37. package/src/index.ts +80 -110
  38. package/src/migration/executor.ts +132 -136
  39. package/src/migration/ledger.ts +95 -97
  40. package/src/migration/validate-chain.ts +65 -65
  41. package/src/platform/backup-android-verify.ts +88 -88
  42. package/src/platform/backup-ios.ts +48 -48
  43. package/src/platform/bytes.ts +144 -144
  44. package/src/platform/fs.ts +89 -89
  45. package/src/platform/location.ts +135 -135
  46. package/src/query/budgets.ts +54 -54
  47. package/src/query/compare.ts +91 -91
  48. package/src/query/cursor.ts +100 -100
  49. package/src/query/filter.ts +82 -82
  50. package/src/query/paginate.ts +51 -51
  51. package/src/query/sort.ts +55 -56
  52. package/src/query/validate.ts +171 -171
  53. package/src/schema/declare.ts +42 -45
  54. package/src/schema/validate-config.ts +158 -158
  55. package/src/scope/hash.ts +131 -134
  56. package/src/scope/normalize.ts +83 -90
  57. package/src/scope/validate.ts +83 -83
  58. package/src/sqlite/adapter.ts +26 -26
  59. package/src/sqlite/batch.ts +242 -242
  60. package/src/sqlite/catalog.ts +105 -108
  61. package/src/sqlite/connection.ts +227 -227
  62. package/src/sqlite/index-coverage.ts +54 -56
  63. package/src/sqlite/native.ts +83 -83
  64. package/src/sqlite/row-codec.ts +217 -221
  65. package/src/sqlite/sql-builder.ts +244 -244
  66. package/src/sqlite/transaction-queue.ts +87 -87
  67. package/src/sqlite/transactions.ts +33 -33
  68. package/src/sqlite/values.ts +24 -24
  69. package/src/testing/conformance/attachments.ts +138 -136
  70. package/src/testing/conformance/encryption.ts +150 -150
  71. package/src/testing/conformance/index.ts +161 -161
  72. package/src/testing/conformance/lifecycle.ts +99 -99
  73. package/src/testing/conformance/migrations.ts +177 -177
  74. package/src/testing/conformance/queries.ts +204 -167
  75. package/src/testing/conformance/records.ts +169 -169
  76. package/src/testing/conformance/scopes.ts +86 -88
  77. package/src/testing/conformance/transactions.ts +149 -149
  78. package/src/testing/faults.ts +21 -21
  79. package/src/testing/index.ts +17 -21
  80. package/src/testing/memory-adapter/collection.ts +214 -219
  81. package/src/testing/memory-adapter/index.ts +510 -492
  82. package/src/testing/memory-adapter/migrations.ts +117 -119
  83. package/src/testing/memory-adapter/reset.ts +50 -50
  84. package/src/testing/memory-adapter/store.ts +128 -134
@@ -1,56 +1,54 @@
1
- /**
2
- * "Is this query covered by an index?", answered by the engine rather than by
3
- * inspection of the declarations (FR-037).
4
- *
5
- * Reading the schema would answer what the developer intended; `EXPLAIN QUERY
6
- * PLAN` answers what SQLite will actually do, and the two diverge exactly
7
- * where it matters — a filter on an indexed field that the planner declines
8
- * because of a type mismatch or a leading wildcard still reports as "indexed"
9
- * if you only read declarations.
10
- *
11
- * Exposed as a development-mode diagnostic. It runs an extra statement, so an
12
- * app calling it on every list would pay for it.
13
- */
14
-
15
- import type { IndexCoverageReport, Query, SortKey } from '../contract/query';
16
- import type { CollectionSchema } from '../contract/schema';
17
- import { collectionTable } from '../schema/declare';
18
- import type { Executor } from './connection';
19
- import { buildFilter, buildOrderBy } from './sql-builder';
20
- import { declaredIndexName } from './row-codec';
21
-
22
- const SCAN = /\bSCAN\b/i;
23
- const USING_INDEX = /USING\s+(COVERING\s+)?INDEX\s+"?([A-Za-z0-9_]+)"?/i;
24
-
25
- export async function explainQuery(
26
- executor: Executor,
27
- schema: CollectionSchema,
28
- query: Query | undefined,
29
- sort: SortKey[],
30
- ): Promise<IndexCoverageReport> {
31
- const filter = buildFilter(query?.filter);
32
- const table = collectionTable(schema.name);
33
- const sql = `EXPLAIN QUERY PLAN SELECT id FROM "${table}" WHERE ${filter.sql} ORDER BY ${buildOrderBy(
34
- sort,
35
- )}`;
36
-
37
- const result = await executor.execute(sql, filter.params);
38
- const detail = result.rows
39
- .map(row => String(row.detail ?? row.DETAIL ?? ''))
40
- .filter(Boolean)
41
- .join('; ');
42
-
43
- const match = USING_INDEX.exec(detail);
44
- return {
45
- collection: schema.name,
46
- usesIndex: match !== null,
47
- // Reported under the name the app declared, not the collection-scoped
48
- // name it has in the file.
49
- indexName: match?.[2] === undefined ? undefined : declaredIndexName(schema, match[2]),
50
- detail:
51
- detail ||
52
- (SCAN.test(detail)
53
- ? 'full table scan'
54
- : 'the engine returned no plan for this query'),
55
- };
56
- }
1
+ /**
2
+ * "Is this query covered by an index?", answered by the engine rather than by
3
+ * inspection of the declarations (FR-037).
4
+ *
5
+ * Reading the schema would answer what the developer intended; `EXPLAIN QUERY
6
+ * PLAN` answers what SQLite will actually do, and the two diverge exactly
7
+ * where it matters — a filter on an indexed field that the planner declines
8
+ * because of a type mismatch or a leading wildcard still reports as "indexed"
9
+ * if you only read declarations.
10
+ *
11
+ * Exposed as a development-mode diagnostic. It runs an extra statement, so an
12
+ * app calling it on every list would pay for it.
13
+ */
14
+
15
+ import type { IndexCoverageReport, Query, SortKey } from '../contract/query';
16
+ import type { CollectionSchema } from '../contract/schema';
17
+ import { collectionTable } from '../schema/declare';
18
+ import type { Executor } from './connection';
19
+ import { buildFilter, buildOrderBy } from './sql-builder';
20
+ import { declaredIndexName } from './row-codec';
21
+
22
+ const SCAN = /\bSCAN\b/i;
23
+ const USING_INDEX = /USING\s+(COVERING\s+)?INDEX\s+"?([A-Za-z0-9_]+)"?/i;
24
+
25
+ export async function explainQuery(
26
+ executor: Executor,
27
+ schema: CollectionSchema,
28
+ query: Query | undefined,
29
+ sort: SortKey[],
30
+ ): Promise<IndexCoverageReport> {
31
+ const filter = buildFilter(query?.filter);
32
+ const table = collectionTable(schema.name);
33
+ const sql = `EXPLAIN QUERY PLAN SELECT id FROM "${table}" WHERE ${filter.sql} ORDER BY ${buildOrderBy(
34
+ sort,
35
+ )}`;
36
+
37
+ const result = await executor.execute(sql, filter.params);
38
+ const detail = result.rows
39
+ .map(row => String(row.detail ?? row.DETAIL ?? ''))
40
+ .filter(Boolean)
41
+ .join('; ');
42
+
43
+ const match = USING_INDEX.exec(detail);
44
+ return {
45
+ collection: schema.name,
46
+ usesIndex: match !== null,
47
+ // Reported under the name the app declared, not the collection-scoped
48
+ // name it has in the file.
49
+ indexName: match?.[2] === undefined ? undefined : declaredIndexName(schema, match[2]),
50
+ detail:
51
+ detail ||
52
+ (SCAN.test(detail) ? 'full table scan' : 'the engine returned no plan for this query'),
53
+ };
54
+ }
@@ -1,83 +1,83 @@
1
- /**
2
- * The storage engine peer, behind one lazy accessor.
3
- *
4
- * Every native call the package makes to SQLite passes through this type.
5
- * Note what is missing: `executeSync`. op-sqlite ships it, and using it would
6
- * put a database read on the interface thread — the one thing PERF-001's
7
- * 16-millisecond rule cannot survive (research.md R9). A surface that cannot
8
- * express the synchronous call is a stronger guarantee than a review comment.
9
- */
10
-
11
- import { DiagnosticCodes } from '../contract/errors';
12
- import { raise } from '../diagnostics/errors';
13
-
14
- export type SqlValue = string | number | boolean | null | undefined;
15
-
16
- export type ExecuteResult = {
17
- rows: Record<string, unknown>[];
18
- rowsAffected: number;
19
- };
20
-
21
- export type NativeTransaction = {
22
- execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
23
- commit(): Promise<void>;
24
- rollback(): Promise<void>;
25
- };
26
-
27
- export type NativeDatabase = {
28
- execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
29
- executeBatch(commands: [string, SqlValue[]?][]): Promise<ExecuteResult>;
30
- transaction<T>(fn: (tx: NativeTransaction) => Promise<T>): Promise<T>;
31
- close(): Promise<void>;
32
- delete(): Promise<void>;
33
- };
34
-
35
- export type OpenOptions = {
36
- name: string;
37
- location?: string;
38
- /** SQLCipher only. Never logged, never persisted, never returned (FR-098). */
39
- encryptionKey?: string;
40
- };
41
-
42
- export type Engine = {
43
- open(options: OpenOptions): NativeDatabase;
44
- isSQLCipher?: () => boolean;
45
- };
46
-
47
- let override: Engine | null = null;
48
- let cached: Engine | null = null;
49
-
50
- /** Swap the engine. Tests only. */
51
- export function setEngine(next: Engine | null): void {
52
- override = next;
53
- cached = null;
54
- }
55
-
56
- export function engine(): Engine {
57
- if (override) {
58
- return override;
59
- }
60
- if (!cached) {
61
- try {
62
- cached = require('@op-engineering/op-sqlite') as Engine;
63
- } catch (error) {
64
- raise(DiagnosticCodes.OPEN_FAILED, {
65
- message: 'storage engine peer is not installed',
66
- cause: error,
67
- });
68
- }
69
- }
70
- return cached!;
71
- }
72
-
73
- /**
74
- * Whether SQLCipher is compiled into this build.
75
- *
76
- * A build flag in the consuming app, not a separate peer (research.md R1a).
77
- * An app that enables encryption without it must fail loudly rather than
78
- * write plaintext to a file it believes is encrypted.
79
- */
80
- export function isEncryptionAvailable(): boolean {
81
- const current = engine();
82
- return typeof current.isSQLCipher === 'function' ? current.isSQLCipher() === true : false;
83
- }
1
+ /**
2
+ * The storage engine peer, behind one lazy accessor.
3
+ *
4
+ * Every native call the package makes to SQLite passes through this type.
5
+ * Note what is missing: `executeSync`. op-sqlite ships it, and using it would
6
+ * put a database read on the interface thread — the one thing PERF-001's
7
+ * 16-millisecond rule cannot survive (research.md R9). A surface that cannot
8
+ * express the synchronous call is a stronger guarantee than a review comment.
9
+ */
10
+
11
+ import { DiagnosticCodes } from '../contract/errors';
12
+ import { raise } from '../diagnostics/errors';
13
+
14
+ export type SqlValue = string | number | boolean | null | undefined;
15
+
16
+ export type ExecuteResult = {
17
+ rows: Record<string, unknown>[];
18
+ rowsAffected: number;
19
+ };
20
+
21
+ export type NativeTransaction = {
22
+ execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
23
+ commit(): Promise<void>;
24
+ rollback(): Promise<void>;
25
+ };
26
+
27
+ export type NativeDatabase = {
28
+ execute(sql: string, params?: SqlValue[]): Promise<ExecuteResult>;
29
+ executeBatch(commands: [string, SqlValue[]?][]): Promise<ExecuteResult>;
30
+ transaction<T>(fn: (tx: NativeTransaction) => Promise<T>): Promise<T>;
31
+ close(): Promise<void>;
32
+ delete(): Promise<void>;
33
+ };
34
+
35
+ export type OpenOptions = {
36
+ name: string;
37
+ location?: string;
38
+ /** SQLCipher only. Never logged, never persisted, never returned (FR-098). */
39
+ encryptionKey?: string;
40
+ };
41
+
42
+ export type Engine = {
43
+ open(options: OpenOptions): NativeDatabase;
44
+ isSQLCipher?: () => boolean;
45
+ };
46
+
47
+ let override: Engine | null = null;
48
+ let cached: Engine | null = null;
49
+
50
+ /** Swap the engine. Tests only. */
51
+ export function setEngine(next: Engine | null): void {
52
+ override = next;
53
+ cached = null;
54
+ }
55
+
56
+ export function engine(): Engine {
57
+ if (override) {
58
+ return override;
59
+ }
60
+ if (!cached) {
61
+ try {
62
+ cached = require('@op-engineering/op-sqlite') as Engine;
63
+ } catch (error) {
64
+ raise(DiagnosticCodes.OPEN_FAILED, {
65
+ message: 'storage engine peer is not installed',
66
+ cause: error,
67
+ });
68
+ }
69
+ }
70
+ return cached!;
71
+ }
72
+
73
+ /**
74
+ * Whether SQLCipher is compiled into this build.
75
+ *
76
+ * A build flag in the consuming app, not a separate peer (research.md R1a).
77
+ * An app that enables encryption without it must fail loudly rather than
78
+ * write plaintext to a file it believes is encrypted.
79
+ */
80
+ export function isEncryptionAvailable(): boolean {
81
+ const current = engine();
82
+ return typeof current.isSQLCipher === 'function' ? current.isSQLCipher() === true : false;
83
+ }