@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,144 +1,144 @@
1
- /**
2
- * Byte handling without Node's `Buffer`.
3
- *
4
- * Hermes has no global `Buffer`, and `react-native-quick-crypto` installs one
5
- * only when the app calls its `install()` — which nothing in this package or its
6
- * documentation asked for. Until 0.2.0 attachment writes and reads used the
7
- * global anyway, so on a device every attachment operation threw. Everything
8
- * here is plain `Uint8Array` and a self-contained base64 codec, identical on
9
- * Hermes and in Node.
10
- */
11
-
12
- const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
13
- const LOOKUP = (() => {
14
- const table = new Int16Array(128).fill(-1);
15
- for (let index = 0; index < ALPHABET.length; index += 1) {
16
- table[ALPHABET.charCodeAt(index)] = index;
17
- }
18
- // The URL-safe variants decode too, since some file APIs return them.
19
- table['-'.charCodeAt(0)] = 62;
20
- table['_'.charCodeAt(0)] = 63;
21
- return table;
22
- })();
23
-
24
- /* eslint-disable no-bitwise -- base64 is bit packing; there is no clearer way to write it. */
25
-
26
- /** Characters per `String.fromCharCode` call; large spreads overflow the stack. */
27
- const CHUNK = 0x8000;
28
-
29
- export function base64ToBytes(base64: string): Uint8Array {
30
- if (typeof globalThis.atob === 'function') {
31
- let binary: string | undefined;
32
- try {
33
- binary = globalThis.atob(base64.replace(/-/g, '+').replace(/_/g, '/'));
34
- } catch {
35
- // atob is strict where Buffer was lenient; fall through to the
36
- // tolerant decoder rather than turn stray characters into a crash.
37
- }
38
- if (binary !== undefined) {
39
- const bytes = new Uint8Array(binary.length);
40
- for (let index = 0; index < binary.length; index += 1) {
41
- bytes[index] = binary.charCodeAt(index);
42
- }
43
- return bytes;
44
- }
45
- }
46
- // Decode directly, skipping padding and anything outside the alphabet.
47
- const bytes = new Uint8Array(Math.ceil((base64.length * 3) / 4));
48
- let out = 0;
49
- let buffer = 0;
50
- let bits = 0;
51
- for (let index = 0; index < base64.length; index += 1) {
52
- const code = base64.charCodeAt(index);
53
- const value = code < 128 ? LOOKUP[code]! : -1;
54
- if (value < 0) continue;
55
- buffer = (buffer << 6) | value;
56
- bits += 6;
57
- if (bits >= 8) {
58
- bits -= 8;
59
- bytes[out++] = (buffer >> bits) & 0xff;
60
- }
61
- }
62
- return bytes.subarray(0, out);
63
- }
64
-
65
- export function bytesToBase64(bytes: Uint8Array): string {
66
- if (typeof globalThis.btoa === 'function') {
67
- const parts: string[] = [];
68
- for (let index = 0; index < bytes.length; index += CHUNK) {
69
- parts.push(String.fromCharCode(...bytes.subarray(index, index + CHUNK)));
70
- }
71
- return globalThis.btoa(parts.join(''));
72
- }
73
- const parts: string[] = [];
74
- for (let start = 0; start < bytes.length; start += CHUNK * 3) {
75
- let piece = '';
76
- const end = Math.min(bytes.length, start + CHUNK * 3);
77
- let index = start;
78
- for (; index + 2 < end; index += 3) {
79
- const triple = (bytes[index]! << 16) | (bytes[index + 1]! << 8) | bytes[index + 2]!;
80
- piece +=
81
- ALPHABET[triple >> 18]! +
82
- ALPHABET[(triple >> 12) & 63]! +
83
- ALPHABET[(triple >> 6) & 63]! +
84
- ALPHABET[triple & 63]!;
85
- }
86
- const remaining = end - index;
87
- if (remaining === 1) {
88
- const value = bytes[index]!;
89
- piece += `${ALPHABET[value >> 2]}${ALPHABET[(value & 3) << 4]}==`;
90
- } else if (remaining === 2) {
91
- const value = (bytes[index]! << 8) | bytes[index + 1]!;
92
- piece += `${ALPHABET[value >> 10]}${ALPHABET[(value >> 4) & 63]}${ALPHABET[(value & 15) << 2]}=`;
93
- }
94
- parts.push(piece);
95
- }
96
- return parts.join('');
97
- }
98
-
99
- /** UTF-8 encoding. `TextEncoder` is in Hermes and Node; the loop is the fallback. */
100
- export function utf8Bytes(value: string): Uint8Array {
101
- if (typeof TextEncoder !== 'undefined') {
102
- return new TextEncoder().encode(value);
103
- }
104
- const encoded = unescape(encodeURIComponent(value));
105
- const bytes = new Uint8Array(encoded.length);
106
- for (let index = 0; index < encoded.length; index += 1) {
107
- bytes[index] = encoded.charCodeAt(index);
108
- }
109
- return bytes;
110
- }
111
-
112
- /** UTF-8 decoding, the inverse of `utf8Bytes`. */
113
- export function utf8String(bytes: Uint8Array): string {
114
- if (typeof TextDecoder !== 'undefined') {
115
- return new TextDecoder().decode(bytes);
116
- }
117
- let binary = '';
118
- for (const byte of bytes) {
119
- binary += String.fromCharCode(byte);
120
- }
121
- return decodeURIComponent(escape(binary));
122
- }
123
-
124
- /* eslint-enable no-bitwise */
125
-
126
- export function concatBytes(parts: readonly Uint8Array[]): Uint8Array {
127
- const total = parts.reduce((sum, part) => sum + part.length, 0);
128
- const joined = new Uint8Array(total);
129
- let offset = 0;
130
- for (const part of parts) {
131
- joined.set(part, offset);
132
- offset += part.length;
133
- }
134
- return joined;
135
- }
136
-
137
- /** The ASCII text of `length` bytes from `start`, for magic numbers. */
138
- export function asciiAt(bytes: Uint8Array, start: number, length: number): string {
139
- let text = '';
140
- for (let index = start; index < start + length && index < bytes.length; index += 1) {
141
- text += String.fromCharCode(bytes[index]!);
142
- }
143
- return text;
144
- }
1
+ /**
2
+ * Byte handling without Node's `Buffer`.
3
+ *
4
+ * Hermes has no global `Buffer`, and `react-native-quick-crypto` installs one
5
+ * only when the app calls its `install()` — which nothing in this package or its
6
+ * documentation asked for. Until 0.2.0 attachment writes and reads used the
7
+ * global anyway, so on a device every attachment operation threw. Everything
8
+ * here is plain `Uint8Array` and a self-contained base64 codec, identical on
9
+ * Hermes and in Node.
10
+ */
11
+
12
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
13
+ const LOOKUP = (() => {
14
+ const table = new Int16Array(128).fill(-1);
15
+ for (let index = 0; index < ALPHABET.length; index += 1) {
16
+ table[ALPHABET.charCodeAt(index)] = index;
17
+ }
18
+ // The URL-safe variants decode too, since some file APIs return them.
19
+ table['-'.charCodeAt(0)] = 62;
20
+ table['_'.charCodeAt(0)] = 63;
21
+ return table;
22
+ })();
23
+
24
+ /* eslint-disable no-bitwise -- base64 is bit packing; there is no clearer way to write it. */
25
+
26
+ /** Characters per `String.fromCharCode` call; large spreads overflow the stack. */
27
+ const CHUNK = 0x8000;
28
+
29
+ export function base64ToBytes(base64: string): Uint8Array {
30
+ if (typeof globalThis.atob === 'function') {
31
+ let binary: string | undefined;
32
+ try {
33
+ binary = globalThis.atob(base64.replace(/-/g, '+').replace(/_/g, '/'));
34
+ } catch {
35
+ // atob is strict where Buffer was lenient; fall through to the
36
+ // tolerant decoder rather than turn stray characters into a crash.
37
+ }
38
+ if (binary !== undefined) {
39
+ const bytes = new Uint8Array(binary.length);
40
+ for (let index = 0; index < binary.length; index += 1) {
41
+ bytes[index] = binary.charCodeAt(index);
42
+ }
43
+ return bytes;
44
+ }
45
+ }
46
+ // Decode directly, skipping padding and anything outside the alphabet.
47
+ const bytes = new Uint8Array(Math.ceil((base64.length * 3) / 4));
48
+ let out = 0;
49
+ let buffer = 0;
50
+ let bits = 0;
51
+ for (let index = 0; index < base64.length; index += 1) {
52
+ const code = base64.charCodeAt(index);
53
+ const value = code < 128 ? LOOKUP[code]! : -1;
54
+ if (value < 0) continue;
55
+ buffer = (buffer << 6) | value;
56
+ bits += 6;
57
+ if (bits >= 8) {
58
+ bits -= 8;
59
+ bytes[out++] = (buffer >> bits) & 0xff;
60
+ }
61
+ }
62
+ return bytes.subarray(0, out);
63
+ }
64
+
65
+ export function bytesToBase64(bytes: Uint8Array): string {
66
+ if (typeof globalThis.btoa === 'function') {
67
+ const parts: string[] = [];
68
+ for (let index = 0; index < bytes.length; index += CHUNK) {
69
+ parts.push(String.fromCharCode(...bytes.subarray(index, index + CHUNK)));
70
+ }
71
+ return globalThis.btoa(parts.join(''));
72
+ }
73
+ const parts: string[] = [];
74
+ for (let start = 0; start < bytes.length; start += CHUNK * 3) {
75
+ let piece = '';
76
+ const end = Math.min(bytes.length, start + CHUNK * 3);
77
+ let index = start;
78
+ for (; index + 2 < end; index += 3) {
79
+ const triple = (bytes[index]! << 16) | (bytes[index + 1]! << 8) | bytes[index + 2]!;
80
+ piece +=
81
+ ALPHABET[triple >> 18]! +
82
+ ALPHABET[(triple >> 12) & 63]! +
83
+ ALPHABET[(triple >> 6) & 63]! +
84
+ ALPHABET[triple & 63]!;
85
+ }
86
+ const remaining = end - index;
87
+ if (remaining === 1) {
88
+ const value = bytes[index]!;
89
+ piece += `${ALPHABET[value >> 2]}${ALPHABET[(value & 3) << 4]}==`;
90
+ } else if (remaining === 2) {
91
+ const value = (bytes[index]! << 8) | bytes[index + 1]!;
92
+ piece += `${ALPHABET[value >> 10]}${ALPHABET[(value >> 4) & 63]}${ALPHABET[(value & 15) << 2]}=`;
93
+ }
94
+ parts.push(piece);
95
+ }
96
+ return parts.join('');
97
+ }
98
+
99
+ /** UTF-8 encoding. `TextEncoder` is in Hermes and Node; the loop is the fallback. */
100
+ export function utf8Bytes(value: string): Uint8Array {
101
+ if (typeof TextEncoder !== 'undefined') {
102
+ return new TextEncoder().encode(value);
103
+ }
104
+ const encoded = unescape(encodeURIComponent(value));
105
+ const bytes = new Uint8Array(encoded.length);
106
+ for (let index = 0; index < encoded.length; index += 1) {
107
+ bytes[index] = encoded.charCodeAt(index);
108
+ }
109
+ return bytes;
110
+ }
111
+
112
+ /** UTF-8 decoding, the inverse of `utf8Bytes`. */
113
+ export function utf8String(bytes: Uint8Array): string {
114
+ if (typeof TextDecoder !== 'undefined') {
115
+ return new TextDecoder().decode(bytes);
116
+ }
117
+ let binary = '';
118
+ for (const byte of bytes) {
119
+ binary += String.fromCharCode(byte);
120
+ }
121
+ return decodeURIComponent(escape(binary));
122
+ }
123
+
124
+ /* eslint-enable no-bitwise */
125
+
126
+ export function concatBytes(parts: readonly Uint8Array[]): Uint8Array {
127
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
128
+ const joined = new Uint8Array(total);
129
+ let offset = 0;
130
+ for (const part of parts) {
131
+ joined.set(part, offset);
132
+ offset += part.length;
133
+ }
134
+ return joined;
135
+ }
136
+
137
+ /** The ASCII text of `length` bytes from `start`, for magic numbers. */
138
+ export function asciiAt(bytes: Uint8Array, start: number, length: number): string {
139
+ let text = '';
140
+ for (let index = start; index < start + length && index < bytes.length; index += 1) {
141
+ text += String.fromCharCode(bytes[index]!);
142
+ }
143
+ return text;
144
+ }
@@ -1,89 +1,89 @@
1
- /**
2
- * The filesystem peer, behind one lazy accessor.
3
- *
4
- * Required lazily for the same reason the engine is: a consuming app that
5
- * only ever runs the in-memory adapter in tests must not have to install a
6
- * native module to import this package. Confining the require to `platform/`
7
- * also keeps the native surface four directories wide, which is the layering
8
- * rule the ESLint config enforces.
9
- */
10
-
11
- import { DiagnosticCodes } from '../contract/errors';
12
- import { raise } from '../diagnostics/errors';
13
-
14
- export type FileStat = {
15
- path: string;
16
- size: number;
17
- mtime: number;
18
- isFile(): boolean;
19
- isDirectory(): boolean;
20
- };
21
-
22
- export type MkdirOptions = {
23
- /** iOS only. Set at creation so nothing beneath it is ever backed up. */
24
- NSURLIsExcludedFromBackupKey?: boolean;
25
- };
26
-
27
- /**
28
- * Only what the package actually uses. The peer's upload and download
29
- * helpers are deliberately absent from this type: the package must never
30
- * make a network request (FR-084, SC-016), and a surface that cannot express
31
- * one is a stronger guarantee than a rule that says not to.
32
- */
33
- export type FileSystem = {
34
- DocumentDirectoryPath: string;
35
- mkdir(path: string, options?: MkdirOptions): Promise<void>;
36
- writeFile(path: string, contents: string, encoding?: string): Promise<void>;
37
- appendFile(path: string, contents: string, encoding?: string): Promise<void>;
38
- readFile(path: string, encoding?: string): Promise<string>;
39
- /** Chunked read at an offset — what FR-107a's flat memory bound rests on. */
40
- read(path: string, length: number, position: number, encoding?: string): Promise<string>;
41
- stat(path: string): Promise<FileStat>;
42
- readdir(path: string): Promise<string[]>;
43
- unlink(path: string): Promise<void>;
44
- exists(path: string): Promise<boolean>;
45
- };
46
-
47
- let override: FileSystem | null = null;
48
- let cached: FileSystem | null = null;
49
-
50
- /** Swap the peer. Tests and the demo app's in-memory mode only. */
51
- export function setFileSystem(module: FileSystem | null): void {
52
- override = module;
53
- cached = null;
54
- }
55
-
56
- export function fileSystem(): FileSystem {
57
- if (override) {
58
- return override;
59
- }
60
- if (!cached) {
61
- try {
62
- cached = require('@dr.pogodin/react-native-fs') as FileSystem;
63
- } catch (error) {
64
- raise(DiagnosticCodes.STORAGE_ACCESS, {
65
- message: 'filesystem peer is not installed',
66
- cause: error,
67
- });
68
- }
69
- }
70
- return cached!;
71
- }
72
-
73
- export async function pathExists(path: string): Promise<boolean> {
74
- try {
75
- return await fileSystem().exists(path);
76
- } catch {
77
- return false;
78
- }
79
- }
80
-
81
- /** Remove a path, treating "already gone" as success (FR-062). */
82
- export async function removeIfPresent(path: string): Promise<boolean> {
83
- try {
84
- await fileSystem().unlink(path);
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- }
1
+ /**
2
+ * The filesystem peer, behind one lazy accessor.
3
+ *
4
+ * Required lazily for the same reason the engine is: a consuming app that
5
+ * only ever runs the in-memory adapter in tests must not have to install a
6
+ * native module to import this package. Confining the require to `platform/`
7
+ * also keeps the native surface four directories wide, which is the layering
8
+ * rule the ESLint config enforces.
9
+ */
10
+
11
+ import { DiagnosticCodes } from '../contract/errors';
12
+ import { raise } from '../diagnostics/errors';
13
+
14
+ export type FileStat = {
15
+ path: string;
16
+ size: number;
17
+ mtime: number;
18
+ isFile(): boolean;
19
+ isDirectory(): boolean;
20
+ };
21
+
22
+ export type MkdirOptions = {
23
+ /** iOS only. Set at creation so nothing beneath it is ever backed up. */
24
+ NSURLIsExcludedFromBackupKey?: boolean;
25
+ };
26
+
27
+ /**
28
+ * Only what the package actually uses. The peer's upload and download
29
+ * helpers are deliberately absent from this type: the package must never
30
+ * make a network request (FR-084, SC-016), and a surface that cannot express
31
+ * one is a stronger guarantee than a rule that says not to.
32
+ */
33
+ export type FileSystem = {
34
+ DocumentDirectoryPath: string;
35
+ mkdir(path: string, options?: MkdirOptions): Promise<void>;
36
+ writeFile(path: string, contents: string, encoding?: string): Promise<void>;
37
+ appendFile(path: string, contents: string, encoding?: string): Promise<void>;
38
+ readFile(path: string, encoding?: string): Promise<string>;
39
+ /** Chunked read at an offset — what FR-107a's flat memory bound rests on. */
40
+ read(path: string, length: number, position: number, encoding?: string): Promise<string>;
41
+ stat(path: string): Promise<FileStat>;
42
+ readdir(path: string): Promise<string[]>;
43
+ unlink(path: string): Promise<void>;
44
+ exists(path: string): Promise<boolean>;
45
+ };
46
+
47
+ let override: FileSystem | null = null;
48
+ let cached: FileSystem | null = null;
49
+
50
+ /** Swap the peer. Tests and the demo app's in-memory mode only. */
51
+ export function setFileSystem(module: FileSystem | null): void {
52
+ override = module;
53
+ cached = null;
54
+ }
55
+
56
+ export function fileSystem(): FileSystem {
57
+ if (override) {
58
+ return override;
59
+ }
60
+ if (!cached) {
61
+ try {
62
+ cached = require('@dr.pogodin/react-native-fs') as FileSystem;
63
+ } catch (error) {
64
+ raise(DiagnosticCodes.STORAGE_ACCESS, {
65
+ message: 'filesystem peer is not installed',
66
+ cause: error,
67
+ });
68
+ }
69
+ }
70
+ return cached!;
71
+ }
72
+
73
+ export async function pathExists(path: string): Promise<boolean> {
74
+ try {
75
+ return await fileSystem().exists(path);
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /** Remove a path, treating "already gone" as success (FR-062). */
82
+ export async function removeIfPresent(path: string): Promise<boolean> {
83
+ try {
84
+ await fileSystem().unlink(path);
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }