@chipmobilesdk/rn-local-db 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +191 -102
  2. package/README.md +728 -728
  3. package/package.json +62 -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 +75 -81
  32. package/src/diagnostics/dev-log.ts +132 -108
  33. package/src/diagnostics/errors.ts +63 -66
  34. package/src/diagnostics/faults.ts +58 -64
  35. package/src/diagnostics/map-native.ts +108 -106
  36. package/src/diagnostics/redact.ts +119 -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 +49 -48
  43. package/src/platform/bytes.ts +157 -144
  44. package/src/platform/fs.ts +90 -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 +84 -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 +139 -136
  70. package/src/testing/conformance/encryption.ts +151 -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 +511 -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,141 +1,119 @@
1
- /**
2
- * Redaction, applied at construction rather than at logging time.
3
- *
4
- * The distinction matters: an error object that never held a payload cannot
5
- * leak one through a consumer's crash reporter, which is where these leaks
6
- * actually happen in practice. Redacting on the way out of a logger would
7
- * still leave the values sitting on the object for anything else to pick up
8
- * (FR-079, FR-099).
9
- */
10
-
11
- /**
12
- * The allowlist IS the mechanism. Every key here has been reviewed as
13
- * incapable of carrying user data: identifiers the app already knows, sizes,
14
- * counts, and version numbers.
15
- *
16
- * A denylist was tried and removed. It rejected `recordId` because the name
17
- * contains "record", which is the failure mode denylists always have — they
18
- * guess from names. A closed allowlist cannot guess, and adding to it is a
19
- * deliberate act with a reviewer attached.
20
- */
21
- export const SAFE_DETAIL_KEYS = new Set([
22
- 'code',
23
- 'collection',
24
- 'recordId',
25
- 'attachmentId',
26
- 'scopeKey',
27
- 'version',
28
- 'recordedVersion',
29
- 'failingVersion',
30
- 'outcome',
31
- 'operation',
32
- 'adapter',
33
- 'platform',
34
- 'sizeBytes',
35
- 'limitBytes',
36
- 'count',
37
- 'limit',
38
- ]);
39
-
40
- const REDACTED = '[redacted]';
41
-
42
- /**
43
- * Reduce foreign text to an identifier, or to nothing.
44
- *
45
- * "Foreign" means text this package did not author: a SQLite message, a
46
- * filesystem error, an `Error` a consumer's own migration threw. Any of them
47
- * can contain anything — SQLite quotes bound parameters back, and a migration
48
- * that fails on a constraint routinely names the value that violated it.
49
- *
50
- * An earlier version of this tried to keep the readable part: the text before
51
- * the first colon, if it "looked like a class of failure". That heuristic
52
- * fails on the case that matters. `Patient reported chest pain` looks exactly
53
- * like a class of failure — short, no punctuation, no digits — and is a
54
- * medical record. There is no shape test that separates prose describing a
55
- * failure from prose describing a person, so there is no version of the
56
- * heuristic worth keeping.
57
- *
58
- * What survives is a leading `SQLITE_*` / `ENOENT`-style identifier, which is
59
- * a closed vocabulary of engine and libc constants and cannot be a value.
60
- * Everything else becomes `[redacted]`. A developer who needs more can
61
- * reproduce with the development logger; a crash report cannot.
62
- */
63
- const IDENTIFIER_TOKEN = /^([A-Z][A-Z0-9_]{2,})\b/;
64
-
65
- export function redactForeignText(text: string): string {
66
- if (!text) {
67
- return '';
68
- }
69
- const firstLine = text.split('\n')[0]!.trim();
70
- const token = IDENTIFIER_TOKEN.exec(firstLine);
71
- return token ? token[1]! : REDACTED;
72
- }
73
-
74
- const MAX_AUTHORED_MESSAGE = 160;
75
-
76
- /**
77
- * A message this package wrote itself.
78
- *
79
- * Every `raise()` call site passes a string literal, and the handful that
80
- * interpolate do so with a value from a closed set — an operator name, a
81
- * fault point, a sanitized identifier. `authored-messages.test.ts` enforces
82
- * that mechanically against the source, so this is a checked property rather
83
- * than a convention someone has to remember.
84
- *
85
- * So no filtering happens here: filtering a message the package wrote would
86
- * destroy the only readable part of the error for no gain. Foreign text never
87
- * reaches this function — it goes through `redactForeignText` above.
88
- */
89
- export function sanitizeAuthoredMessage(message: string): string {
90
- return message.split('\n')[0]!.trim().slice(0, MAX_AUTHORED_MESSAGE);
91
- }
92
-
93
- /**
94
- * Reduce a consumer-supplied identifier to a bounded, punctuation-free token.
95
- *
96
- * A field name is worth naming — FR-034 exists so a typo is distinguishable
97
- * from a deliberate choice, and "which field?" is the whole answer. But the
98
- * name arrives from the consumer, so it is capped and stripped rather than
99
- * trusted verbatim.
100
- */
101
- export function sanitizeIdentifier(identifier: string): string {
102
- const cleaned = identifier.replace(/\W/g, '').slice(0, 64);
103
- return cleaned.length > 0 ? cleaned : REDACTED;
104
- }
105
-
106
- /** Keep only keys on the allowlist, and only primitive values. */
107
- export function redactDetail(
108
- detail: Record<string, unknown> | undefined,
109
- ): Record<string, string | number | boolean> | undefined {
110
- if (!detail) {
111
- return undefined;
112
- }
113
- const safe: Record<string, string | number | boolean> = {};
114
- for (const [key, value] of Object.entries(detail)) {
115
- if (!SAFE_DETAIL_KEYS.has(key)) {
116
- continue;
117
- }
118
- if (
119
- typeof value === 'string' ||
120
- typeof value === 'number' ||
121
- typeof value === 'boolean'
122
- ) {
123
- safe[key] = value;
124
- }
125
- }
126
- return Object.keys(safe).length > 0 ? safe : undefined;
127
- }
128
-
129
- /** Reduce an unknown thrown value to a message-shaped string, redacted. */
130
- export function redactCause(cause: unknown): string | undefined {
131
- if (cause === undefined || cause === null) {
132
- return undefined;
133
- }
134
- if (cause instanceof Error) {
135
- return redactForeignText(cause.message);
136
- }
137
- if (typeof cause === 'string') {
138
- return redactForeignText(cause);
139
- }
140
- return REDACTED;
141
- }
1
+ /**
2
+ * Redaction, applied at construction rather than at logging time.
3
+ *
4
+ * The distinction matters: an error object that never held a payload cannot
5
+ * leak one through a consumer's crash reporter, which is where these leaks
6
+ * actually happen in practice. Redacting on the way out of a logger would
7
+ * still leave the values sitting on the object for anything else to pick up
8
+ * (FR-079, FR-099).
9
+ */
10
+
11
+ import { REDACTED, leadingIdentifier, pickAllowed } from '@chipmobilesdk/rn-core';
12
+
13
+ /**
14
+ * The allowlist IS the mechanism. Every key here has been reviewed as
15
+ * incapable of carrying user data: identifiers the app already knows, sizes,
16
+ * counts, and version numbers.
17
+ *
18
+ * A denylist was tried and removed. It rejected `recordId` because the name
19
+ * contains "record", which is the failure mode denylists always have — they
20
+ * guess from names. A closed allowlist cannot guess, and adding to it is a
21
+ * deliberate act with a reviewer attached.
22
+ */
23
+ export const SAFE_DETAIL_KEYS = new Set([
24
+ 'code',
25
+ 'collection',
26
+ 'recordId',
27
+ 'attachmentId',
28
+ 'scopeKey',
29
+ 'version',
30
+ 'recordedVersion',
31
+ 'failingVersion',
32
+ 'outcome',
33
+ 'operation',
34
+ 'adapter',
35
+ 'platform',
36
+ 'sizeBytes',
37
+ 'limitBytes',
38
+ 'count',
39
+ 'limit',
40
+ ]);
41
+
42
+ /**
43
+ * Reduce foreign text to an identifier, or to nothing.
44
+ *
45
+ * "Foreign" means text this package did not author: a SQLite message, a
46
+ * filesystem error, an `Error` a consumer's own migration threw. Any of them
47
+ * can contain anything — SQLite quotes bound parameters back, and a migration
48
+ * that fails on a constraint routinely names the value that violated it.
49
+ *
50
+ * An earlier version of this tried to keep the readable part: the text before
51
+ * the first colon, if it "looked like a class of failure". That heuristic
52
+ * fails on the case that matters. `Patient reported chest pain` looks exactly
53
+ * like a class of failure — short, no punctuation, no digits — and is a
54
+ * medical record. There is no shape test that separates prose describing a
55
+ * failure from prose describing a person, so there is no version of the
56
+ * heuristic worth keeping.
57
+ *
58
+ * What survives is a leading `SQLITE_*` / `ENOENT`-style identifier, which is
59
+ * a closed vocabulary of engine and libc constants and cannot be a value.
60
+ * Everything else becomes `[redacted]`. A developer who needs more can
61
+ * reproduce with the development logger; a crash report cannot.
62
+ */
63
+ export function redactForeignText(text: string): string {
64
+ return leadingIdentifier(text);
65
+ }
66
+
67
+ const MAX_AUTHORED_MESSAGE = 160;
68
+
69
+ /**
70
+ * A message this package wrote itself.
71
+ *
72
+ * Every `raise()` call site passes a string literal, and the handful that
73
+ * interpolate do so with a value from a closed set — an operator name, a
74
+ * fault point, a sanitized identifier. `authored-messages.test.ts` enforces
75
+ * that mechanically against the source, so this is a checked property rather
76
+ * than a convention someone has to remember.
77
+ *
78
+ * So no filtering happens here: filtering a message the package wrote would
79
+ * destroy the only readable part of the error for no gain. Foreign text never
80
+ * reaches this function — it goes through `redactForeignText` above.
81
+ */
82
+ export function sanitizeAuthoredMessage(message: string): string {
83
+ return message.split('\n')[0]!.trim().slice(0, MAX_AUTHORED_MESSAGE);
84
+ }
85
+
86
+ /**
87
+ * Reduce a consumer-supplied identifier to a bounded, punctuation-free token.
88
+ *
89
+ * A field name is worth naming — FR-034 exists so a typo is distinguishable
90
+ * from a deliberate choice, and "which field?" is the whole answer. But the
91
+ * name arrives from the consumer, so it is capped and stripped rather than
92
+ * trusted verbatim.
93
+ */
94
+ export function sanitizeIdentifier(identifier: string): string {
95
+ const cleaned = identifier.replace(/\W/g, '').slice(0, 64);
96
+ return cleaned.length > 0 ? cleaned : REDACTED;
97
+ }
98
+
99
+ /** Keep only keys on the allowlist, and only primitive values. */
100
+ export function redactDetail(
101
+ detail: Record<string, unknown> | undefined,
102
+ ): Record<string, string | number | boolean> | undefined {
103
+ const safe = pickAllowed(detail, SAFE_DETAIL_KEYS);
104
+ return safe && Object.keys(safe).length > 0 ? safe : undefined;
105
+ }
106
+
107
+ /** Reduce an unknown thrown value to a message-shaped string, redacted. */
108
+ export function redactCause(cause: unknown): string | undefined {
109
+ if (cause === undefined || cause === null) {
110
+ return undefined;
111
+ }
112
+ if (cause instanceof Error) {
113
+ return redactForeignText(cause.message);
114
+ }
115
+ if (typeof cause === 'string') {
116
+ return redactForeignText(cause);
117
+ }
118
+ return REDACTED;
119
+ }
package/src/index.ts CHANGED
@@ -1,110 +1,80 @@
1
- /**
2
- * `@chipmobilesdk/rn-local-db`
3
- *
4
- * Offline-first, scope-isolated local storage for ChipMobileSdk apps.
5
- *
6
- * What is deliberately absent is part of the contract: there is no
7
- * `switchScope`, and no read, write, or query operation accepts a scope
8
- * argument. Scope is bound to a handle at open time and immutable, so a
9
- * cross-scope leak is not expressible rather than merely prevented.
10
- *
11
- * The underlying engine, the physical layout, key material, and the query
12
- * translation are private and unreachable by deep import.
13
- */
14
-
15
- // Lifecycle
16
- export {
17
- deleteDatabase,
18
- deleteScopeData,
19
- openDatabase,
20
- resetScope,
21
- } from './adapter';
22
-
23
- // Diagnostics
24
- export {
25
- DiagnosticCodes,
26
- StorageError,
27
- isCode,
28
- isStorageError,
29
- } from './diagnostics/errors';
30
- export { setDiagnosticLogger, type DiagnosticLogger } from './diagnostics/dev-log';
31
-
32
- // Budgets, so a consumer can check before it writes rather than after
33
- export { Budgets } from './contract/config';
34
-
35
- // Contract types
36
- export type {
37
- DatabaseConfig,
38
- DatabaseTargetConfig,
39
- ScopeTargetConfig,
40
- } from './contract/config';
41
- export type {
42
- ResolvedScope,
43
- ScopeDescriptor,
44
- ScopeKind,
45
- } from './contract/scope';
46
- export type {
47
- CollectionSchema,
48
- FieldDeclaration,
49
- FieldType,
50
- IndexDeclaration,
51
- } from './contract/schema';
52
- export type {
53
- RecordInput,
54
- StorableRecordData,
55
- StorableValue,
56
- StoredRecord,
57
- WriteOperation,
58
- WriteOutcome,
59
- } from './contract/record';
60
- export type {
61
- ComparisonNode,
62
- FilterNode,
63
- FilterValue,
64
- IndexCoverageReport,
65
- LogicalNode,
66
- PageRequest,
67
- Query,
68
- ResultPage,
69
- SortDirection,
70
- SortKey,
71
- } from './contract/query';
72
- export type {
73
- BatchOperation,
74
- BatchOutcome,
75
- CollectionOps,
76
- Transaction,
77
- } from './contract/transaction';
78
- export type {
79
- Migration,
80
- MigrationContext,
81
- MigrationFailureDetail,
82
- MigrationLedgerEntry,
83
- MigrationOutcome,
84
- } from './contract/migration';
85
- export type {
86
- AttachmentAvailability,
87
- AttachmentAvailabilityState,
88
- AttachmentInput,
89
- AttachmentOwner,
90
- AttachmentReference,
91
- ByteRange,
92
- DeletionOutcome,
93
- ReconciliationReport,
94
- } from './contract/attachment';
95
- export type {
96
- EncryptionConfig,
97
- EncryptionPosture,
98
- KeyProvider,
99
- } from './contract/encryption';
100
- export type {
101
- BackupConfig,
102
- BackupPostureValue,
103
- EffectivePosture,
104
- } from './contract/backup';
105
- export type { DiagnosticCode } from './contract/errors';
106
- export type {
107
- Attachments,
108
- DatabaseHandle,
109
- StorageAdapter,
110
- } from './contract/adapter';
1
+ /**
2
+ * `@chipmobilesdk/rn-local-db`
3
+ *
4
+ * Offline-first, scope-isolated local storage for ChipMobileSdk apps.
5
+ *
6
+ * What is deliberately absent is part of the contract: there is no
7
+ * `switchScope`, and no read, write, or query operation accepts a scope
8
+ * argument. Scope is bound to a handle at open time and immutable, so a
9
+ * cross-scope leak is not expressible rather than merely prevented.
10
+ *
11
+ * The underlying engine, the physical layout, key material, and the query
12
+ * translation are private and unreachable by deep import.
13
+ */
14
+
15
+ // Lifecycle
16
+ export { deleteDatabase, deleteScopeData, openDatabase, resetScope } from './adapter';
17
+
18
+ // Diagnostics
19
+ export { DiagnosticCodes, StorageError, isCode, isStorageError } from './diagnostics/errors';
20
+ export { setDiagnosticLogger, type DiagnosticLogger } from './diagnostics/dev-log';
21
+
22
+ // Budgets, so a consumer can check before it writes rather than after
23
+ export { Budgets } from './contract/config';
24
+
25
+ // Contract types
26
+ export type { DatabaseConfig, DatabaseTargetConfig, ScopeTargetConfig } from './contract/config';
27
+ export type { ResolvedScope, ScopeDescriptor, ScopeKind } from './contract/scope';
28
+ export type {
29
+ CollectionSchema,
30
+ FieldDeclaration,
31
+ FieldType,
32
+ IndexDeclaration,
33
+ } from './contract/schema';
34
+ export type {
35
+ RecordInput,
36
+ StorableRecordData,
37
+ StorableValue,
38
+ StoredRecord,
39
+ WriteOperation,
40
+ WriteOutcome,
41
+ } from './contract/record';
42
+ export type {
43
+ ComparisonNode,
44
+ FilterNode,
45
+ FilterValue,
46
+ IndexCoverageReport,
47
+ LogicalNode,
48
+ PageRequest,
49
+ Query,
50
+ ResultPage,
51
+ SortDirection,
52
+ SortKey,
53
+ } from './contract/query';
54
+ export type {
55
+ BatchOperation,
56
+ BatchOutcome,
57
+ CollectionOps,
58
+ Transaction,
59
+ } from './contract/transaction';
60
+ export type {
61
+ Migration,
62
+ MigrationContext,
63
+ MigrationFailureDetail,
64
+ MigrationLedgerEntry,
65
+ MigrationOutcome,
66
+ } from './contract/migration';
67
+ export type {
68
+ AttachmentAvailability,
69
+ AttachmentAvailabilityState,
70
+ AttachmentInput,
71
+ AttachmentOwner,
72
+ AttachmentReference,
73
+ ByteRange,
74
+ DeletionOutcome,
75
+ ReconciliationReport,
76
+ } from './contract/attachment';
77
+ export type { EncryptionConfig, EncryptionPosture, KeyProvider } from './contract/encryption';
78
+ export type { BackupConfig, BackupPostureValue, EffectivePosture } from './contract/backup';
79
+ export type { DiagnosticCode } from './contract/errors';
80
+ export type { Attachments, DatabaseHandle, StorageAdapter } from './contract/adapter';