@chipmobilesdk/rn-local-db 0.1.2 → 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 -52
  2. package/README.md +663 -661
  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 -238
  8. package/src/adapter/index.ts +64 -68
  9. package/src/adapter/lifecycle.ts +517 -444
  10. package/src/adapter/transaction.ts +71 -65
  11. package/src/artifacts/availability.ts +67 -69
  12. package/src/artifacts/cascade.ts +114 -78
  13. package/src/artifacts/paths.ts +29 -29
  14. package/src/artifacts/reconcile.ts +63 -63
  15. package/src/artifacts/store.ts +334 -323
  16. package/src/contract/adapter.ts +76 -78
  17. package/src/contract/attachment.ts +75 -72
  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 -40
  23. package/src/contract/query.ts +81 -81
  24. package/src/contract/record.ts +46 -42
  25. package/src/contract/schema.ts +36 -36
  26. package/src/contract/scope.ts +36 -36
  27. package/src/contract/transaction.ts +52 -46
  28. package/src/crypto/chunked-cipher.ts +153 -144
  29. package/src/crypto/derive.ts +64 -63
  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 -0
  44. package/src/platform/fs.ts +89 -89
  45. package/src/platform/location.ts +135 -135
  46. package/src/query/budgets.ts +54 -57
  47. package/src/query/compare.ts +91 -91
  48. package/src/query/cursor.ts +100 -99
  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 -232
  60. package/src/sqlite/catalog.ts +105 -108
  61. package/src/sqlite/connection.ts +227 -205
  62. package/src/sqlite/index-coverage.ts +54 -53
  63. package/src/sqlite/native.ts +83 -83
  64. package/src/sqlite/row-codec.ts +217 -201
  65. package/src/sqlite/sql-builder.ts +244 -244
  66. package/src/sqlite/transaction-queue.ts +87 -0
  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 -388
  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,161 +1,161 @@
1
- /**
2
- * The conformance suite: the executable definition of what the package
3
- * guarantees (FR-092).
4
- *
5
- * Exported rather than kept internal so a consuming team can run it against
6
- * their own adapter if one ever exists, and so both of ours are held to
7
- * exactly the same standard rather than to two similar-looking test files.
8
- */
9
-
10
- import type { StorageAdapter } from '../../contract/adapter';
11
- import type { DatabaseConfig, ScopeTargetConfig } from '../../contract/config';
12
- import type { EncryptionConfig } from '../../contract/encryption';
13
- import type { ScopeDescriptor } from '../../contract/scope';
14
- import { registerRecordConformance } from './records';
15
- import { registerScopeConformance } from './scopes';
16
- import { registerQueryConformance } from './queries';
17
- import { registerTransactionConformance } from './transactions';
18
- import { registerMigrationConformance } from './migrations';
19
- import { registerLifecycleConformance } from './lifecycle';
20
- import { registerAttachmentConformance } from './attachments';
21
- import { registerEncryptionConformance } from './encryption';
22
-
23
- export type ConformanceContext = {
24
- adapter: StorageAdapter;
25
- /** A fresh config for the given scope, using the standard test schema. */
26
- configFor(scope: ScopeDescriptor, overrides?: Partial<DatabaseConfig>): DatabaseConfig;
27
- /**
28
- * How an app names a scope for deletion.
29
- *
30
- * Carries the encryption config when the run is encrypted, because that is
31
- * how a real consumer calls it: `ScopeTargetConfig.encryption` exists so
32
- * deletion can report what it removed rather than guessing at it.
33
- */
34
- scopeTargetFor(scope: ScopeDescriptor): ScopeTargetConfig;
35
- reset(): Promise<void>;
36
- /**
37
- * Reach past the package and delete an artifact the way the operating
38
- * system does when it reclaims storage. Each adapter supplies its own,
39
- * because the suite cannot simulate this through the public API without
40
- * asking the package to break its own guarantee.
41
- */
42
- removeArtifactBehindTheScenes(scopeKey: string, attachmentId: string): Promise<void>;
43
- /** The other direction: a reference whose artifact survives. */
44
- removeReferenceBehindTheScenes(scopeKey: string, attachmentId: string): Promise<void>;
45
- };
46
-
47
- export type ConformanceHooks = Pick<
48
- ConformanceContext,
49
- 'removeArtifactBehindTheScenes' | 'removeReferenceBehindTheScenes'
50
- >;
51
-
52
- export type ConformanceOptions = {
53
- /** Sections the adapter knowingly cannot satisfy, with a documented reason. */
54
- skip?: ConformanceSection[];
55
- /**
56
- * Run every section against an encrypted database (FR-105).
57
- *
58
- * The suite is the contract, so the contract has to hold in both postures
59
- * — an encrypted database that quietly behaved differently would be a
60
- * second implementation nobody tested.
61
- */
62
- encryption?: EncryptionConfig;
63
- };
64
-
65
- export type ConformanceSection =
66
- | 'records'
67
- | 'scopes'
68
- | 'queries'
69
- | 'transactions'
70
- | 'migrations'
71
- | 'lifecycle'
72
- | 'attachments'
73
- | 'encryption';
74
-
75
- export const TEST_COLLECTIONS: DatabaseConfig['collections'] = [
76
- {
77
- name: 'notes',
78
- fields: [
79
- { name: 'title', type: 'string' },
80
- { name: 'score', type: 'number' },
81
- { name: 'done', type: 'boolean' },
82
- { name: 'updated', type: 'timestamp' },
83
- ],
84
- indexes: [{ name: 'notes_by_score', fields: ['score'] }],
85
- softDelete: true,
86
- timestamps: true,
87
- },
88
- {
89
- name: 'plain',
90
- fields: [{ name: 'label', type: 'string' }],
91
- },
92
- ];
93
-
94
- export function makeConformanceContext(
95
- adapter: StorageAdapter,
96
- reset: () => Promise<void>,
97
- hooks: ConformanceHooks,
98
- defaults: Partial<DatabaseConfig> = {},
99
- ): ConformanceContext {
100
- return {
101
- adapter,
102
- reset,
103
- ...hooks,
104
- configFor(scope, overrides = {}) {
105
- return {
106
- name: 'conformance',
107
- scope,
108
- schemaVersion: 1,
109
- collections: TEST_COLLECTIONS,
110
- migrations: [{ version: 1, migrate: async () => {} }],
111
- ...defaults,
112
- ...overrides,
113
- };
114
- },
115
- scopeTargetFor(scope) {
116
- return {
117
- name: 'conformance',
118
- scope,
119
- ...(defaults.encryption ? { encryption: defaults.encryption } : {}),
120
- };
121
- },
122
- };
123
- }
124
-
125
- /**
126
- * Registers the whole suite against one adapter. Both adapters call this with
127
- * the same arguments; any difference in outcome is a divergence that must be
128
- * documented under FR-096 or fixed.
129
- */
130
- export function runConformanceSuite(
131
- factory: () => StorageAdapter,
132
- reset: () => Promise<void>,
133
- hooks: ConformanceHooks,
134
- options: ConformanceOptions = {},
135
- ): void {
136
- const skip = new Set(options.skip ?? []);
137
- const context = makeConformanceContext(
138
- factory(),
139
- reset,
140
- hooks,
141
- options.encryption ? { encryption: options.encryption } : {},
142
- );
143
-
144
- const sections: [ConformanceSection, (c: ConformanceContext) => void][] = [
145
- ['records', registerRecordConformance],
146
- ['scopes', registerScopeConformance],
147
- ['queries', registerQueryConformance],
148
- ['transactions', registerTransactionConformance],
149
- ['migrations', registerMigrationConformance],
150
- ['lifecycle', registerLifecycleConformance],
151
- ['attachments', registerAttachmentConformance],
152
- ['encryption', registerEncryptionConformance],
153
- ];
154
-
155
- for (const [name, register] of sections) {
156
- if (skip.has(name)) {
157
- continue;
158
- }
159
- register(context);
160
- }
161
- }
1
+ /**
2
+ * The conformance suite: the executable definition of what the package
3
+ * guarantees (FR-092).
4
+ *
5
+ * Exported rather than kept internal so a consuming team can run it against
6
+ * their own adapter if one ever exists, and so both of ours are held to
7
+ * exactly the same standard rather than to two similar-looking test files.
8
+ */
9
+
10
+ import type { StorageAdapter } from '../../contract/adapter';
11
+ import type { DatabaseConfig, ScopeTargetConfig } from '../../contract/config';
12
+ import type { EncryptionConfig } from '../../contract/encryption';
13
+ import type { ScopeDescriptor } from '../../contract/scope';
14
+ import { registerRecordConformance } from './records';
15
+ import { registerScopeConformance } from './scopes';
16
+ import { registerQueryConformance } from './queries';
17
+ import { registerTransactionConformance } from './transactions';
18
+ import { registerMigrationConformance } from './migrations';
19
+ import { registerLifecycleConformance } from './lifecycle';
20
+ import { registerAttachmentConformance } from './attachments';
21
+ import { registerEncryptionConformance } from './encryption';
22
+
23
+ export type ConformanceContext = {
24
+ adapter: StorageAdapter;
25
+ /** A fresh config for the given scope, using the standard test schema. */
26
+ configFor(scope: ScopeDescriptor, overrides?: Partial<DatabaseConfig>): DatabaseConfig;
27
+ /**
28
+ * How an app names a scope for deletion.
29
+ *
30
+ * Carries the encryption config when the run is encrypted, because that is
31
+ * how a real consumer calls it: `ScopeTargetConfig.encryption` exists so
32
+ * deletion can report what it removed rather than guessing at it.
33
+ */
34
+ scopeTargetFor(scope: ScopeDescriptor): ScopeTargetConfig;
35
+ reset(): Promise<void>;
36
+ /**
37
+ * Reach past the package and delete an artifact the way the operating
38
+ * system does when it reclaims storage. Each adapter supplies its own,
39
+ * because the suite cannot simulate this through the public API without
40
+ * asking the package to break its own guarantee.
41
+ */
42
+ removeArtifactBehindTheScenes(scopeKey: string, attachmentId: string): Promise<void>;
43
+ /** The other direction: a reference whose artifact survives. */
44
+ removeReferenceBehindTheScenes(scopeKey: string, attachmentId: string): Promise<void>;
45
+ };
46
+
47
+ export type ConformanceHooks = Pick<
48
+ ConformanceContext,
49
+ 'removeArtifactBehindTheScenes' | 'removeReferenceBehindTheScenes'
50
+ >;
51
+
52
+ export type ConformanceOptions = {
53
+ /** Sections the adapter knowingly cannot satisfy, with a documented reason. */
54
+ skip?: ConformanceSection[];
55
+ /**
56
+ * Run every section against an encrypted database (FR-105).
57
+ *
58
+ * The suite is the contract, so the contract has to hold in both postures
59
+ * — an encrypted database that quietly behaved differently would be a
60
+ * second implementation nobody tested.
61
+ */
62
+ encryption?: EncryptionConfig;
63
+ };
64
+
65
+ export type ConformanceSection =
66
+ | 'records'
67
+ | 'scopes'
68
+ | 'queries'
69
+ | 'transactions'
70
+ | 'migrations'
71
+ | 'lifecycle'
72
+ | 'attachments'
73
+ | 'encryption';
74
+
75
+ export const TEST_COLLECTIONS: DatabaseConfig['collections'] = [
76
+ {
77
+ name: 'notes',
78
+ fields: [
79
+ { name: 'title', type: 'string' },
80
+ { name: 'score', type: 'number' },
81
+ { name: 'done', type: 'boolean' },
82
+ { name: 'updated', type: 'timestamp' },
83
+ ],
84
+ indexes: [{ name: 'notes_by_score', fields: ['score'] }],
85
+ softDelete: true,
86
+ timestamps: true,
87
+ },
88
+ {
89
+ name: 'plain',
90
+ fields: [{ name: 'label', type: 'string' }],
91
+ },
92
+ ];
93
+
94
+ export function makeConformanceContext(
95
+ adapter: StorageAdapter,
96
+ reset: () => Promise<void>,
97
+ hooks: ConformanceHooks,
98
+ defaults: Partial<DatabaseConfig> = {},
99
+ ): ConformanceContext {
100
+ return {
101
+ adapter,
102
+ reset,
103
+ ...hooks,
104
+ configFor(scope, overrides = {}) {
105
+ return {
106
+ name: 'conformance',
107
+ scope,
108
+ schemaVersion: 1,
109
+ collections: TEST_COLLECTIONS,
110
+ migrations: [{ version: 1, migrate: async () => {} }],
111
+ ...defaults,
112
+ ...overrides,
113
+ };
114
+ },
115
+ scopeTargetFor(scope) {
116
+ return {
117
+ name: 'conformance',
118
+ scope,
119
+ ...(defaults.encryption ? { encryption: defaults.encryption } : {}),
120
+ };
121
+ },
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Registers the whole suite against one adapter. Both adapters call this with
127
+ * the same arguments; any difference in outcome is a divergence that must be
128
+ * documented under FR-096 or fixed.
129
+ */
130
+ export function runConformanceSuite(
131
+ factory: () => StorageAdapter,
132
+ reset: () => Promise<void>,
133
+ hooks: ConformanceHooks,
134
+ options: ConformanceOptions = {},
135
+ ): void {
136
+ const skip = new Set(options.skip ?? []);
137
+ const context = makeConformanceContext(
138
+ factory(),
139
+ reset,
140
+ hooks,
141
+ options.encryption ? { encryption: options.encryption } : {},
142
+ );
143
+
144
+ const sections: [ConformanceSection, (c: ConformanceContext) => void][] = [
145
+ ['records', registerRecordConformance],
146
+ ['scopes', registerScopeConformance],
147
+ ['queries', registerQueryConformance],
148
+ ['transactions', registerTransactionConformance],
149
+ ['migrations', registerMigrationConformance],
150
+ ['lifecycle', registerLifecycleConformance],
151
+ ['attachments', registerAttachmentConformance],
152
+ ['encryption', registerEncryptionConformance],
153
+ ];
154
+
155
+ for (const [name, register] of sections) {
156
+ if (skip.has(name)) {
157
+ continue;
158
+ }
159
+ register(context);
160
+ }
161
+ }
@@ -1,99 +1,99 @@
1
- import type { ScopeDescriptor } from '../../contract/scope';
2
- import type { ConformanceContext } from './index';
3
-
4
- /** US6: sign-out retains; deletion happens only when asked, and only where asked. */
5
- export function registerLifecycleConformance(context: ConformanceContext): void {
6
- describe('conformance: lifecycle', () => {
7
- beforeEach(() => context.reset());
8
-
9
- const alice: ScopeDescriptor = { kind: 'identity', id: 'alice' };
10
- const bob: ScopeDescriptor = { kind: 'identity', id: 'bob' };
11
-
12
- it('retains data when a handle is closed without a delete request', async () => {
13
- const first = await context.adapter.open(context.configFor(alice));
14
- await first.collection('notes').insert({ id: 'a', data: { title: 'kept' } });
15
- // Closing the handle is what a sign-out looks like to this package.
16
- await first.close();
17
-
18
- const second = await context.adapter.open(context.configFor(alice));
19
- expect((await second.collection('notes').get('a')).data.title).toBe('kept');
20
- await second.close();
21
- });
22
-
23
- it('removes every record in the target scope and nothing else', async () => {
24
- const a = await context.adapter.open(context.configFor(alice));
25
- await a.collection('notes').insert({ id: 'x', data: { title: 'alice' } });
26
- await a.collection('plain').insert({ id: 'y', data: { label: 'alice' } });
27
- await a.close();
28
-
29
- const b = await context.adapter.open(context.configFor(bob));
30
- await b.collection('notes').insert({ id: 'x', data: { title: 'bob' } });
31
- await b.close();
32
-
33
- const outcome = await context.adapter.deleteScopeData(context.scopeTargetFor(alice));
34
- expect(outcome.removedRecords).toBe(2);
35
-
36
- const reopenedAlice = await context.adapter.open(context.configFor(alice));
37
- expect(await reopenedAlice.collection('notes').count()).toBe(0);
38
- expect(await reopenedAlice.collection('plain').count()).toBe(0);
39
- await reopenedAlice.close();
40
-
41
- const reopenedBob = await context.adapter.open(context.configFor(bob));
42
- expect((await reopenedBob.collection('notes').get('x')).data.title).toBe('bob');
43
- await reopenedBob.close();
44
- });
45
-
46
- it('reports success for a scope holding no data', async () => {
47
- const outcome = await context.adapter.deleteScopeData(
48
- context.scopeTargetFor({ kind: 'identity', id: 'never-used' }),
49
- );
50
- expect(outcome.removedRecords).toBe(0);
51
- expect(outcome.failedRemovals).toEqual([]);
52
- });
53
-
54
- it('reports what it removed rather than a bare success', async () => {
55
- const handle = await context.adapter.open(context.configFor(alice));
56
- await handle.collection('notes').insert({ id: 'a', data: { title: 'x' } });
57
- await handle.close();
58
-
59
- const outcome = await context.adapter.deleteScopeData(context.scopeTargetFor(alice));
60
- expect(outcome).toMatchObject({
61
- removedRecords: 1,
62
- removedArtifacts: [],
63
- orphanedReferences: [],
64
- failedRemovals: [],
65
- });
66
- });
67
-
68
- it('makes the whole database behave as a first install after deleteDatabase', async () => {
69
- const handle = await context.adapter.open(context.configFor(alice));
70
- await handle.collection('notes').insert({ id: 'a', data: { title: 'x' } });
71
- await handle.close();
72
-
73
- await context.adapter.deleteDatabase({ name: 'conformance' });
74
-
75
- const reopened = await context.adapter.open(context.configFor(alice));
76
- expect(await reopened.collection('notes').count()).toBe(0);
77
- await reopened.close();
78
- });
79
-
80
- it('recovers exactly one scope with resetScope', async () => {
81
- const a = await context.adapter.open(context.configFor(alice));
82
- await a.collection('notes').insert({ id: 'x', data: { title: 'alice' } });
83
- await a.close();
84
- const b = await context.adapter.open(context.configFor(bob));
85
- await b.collection('notes').insert({ id: 'x', data: { title: 'bob' } });
86
- await b.close();
87
-
88
- await context.adapter.resetScope(context.scopeTargetFor(alice));
89
-
90
- const reopenedAlice = await context.adapter.open(context.configFor(alice));
91
- expect(await reopenedAlice.collection('notes').count()).toBe(0);
92
- await reopenedAlice.close();
93
-
94
- const reopenedBob = await context.adapter.open(context.configFor(bob));
95
- expect(await reopenedBob.collection('notes').count()).toBe(1);
96
- await reopenedBob.close();
97
- });
98
- });
99
- }
1
+ import type { ScopeDescriptor } from '../../contract/scope';
2
+ import type { ConformanceContext } from './index';
3
+
4
+ /** US6: sign-out retains; deletion happens only when asked, and only where asked. */
5
+ export function registerLifecycleConformance(context: ConformanceContext): void {
6
+ describe('conformance: lifecycle', () => {
7
+ beforeEach(() => context.reset());
8
+
9
+ const alice: ScopeDescriptor = { kind: 'identity', id: 'alice' };
10
+ const bob: ScopeDescriptor = { kind: 'identity', id: 'bob' };
11
+
12
+ it('retains data when a handle is closed without a delete request', async () => {
13
+ const first = await context.adapter.open(context.configFor(alice));
14
+ await first.collection('notes').insert({ id: 'a', data: { title: 'kept' } });
15
+ // Closing the handle is what a sign-out looks like to this package.
16
+ await first.close();
17
+
18
+ const second = await context.adapter.open(context.configFor(alice));
19
+ expect((await second.collection('notes').get('a')).data.title).toBe('kept');
20
+ await second.close();
21
+ });
22
+
23
+ it('removes every record in the target scope and nothing else', async () => {
24
+ const a = await context.adapter.open(context.configFor(alice));
25
+ await a.collection('notes').insert({ id: 'x', data: { title: 'alice' } });
26
+ await a.collection('plain').insert({ id: 'y', data: { label: 'alice' } });
27
+ await a.close();
28
+
29
+ const b = await context.adapter.open(context.configFor(bob));
30
+ await b.collection('notes').insert({ id: 'x', data: { title: 'bob' } });
31
+ await b.close();
32
+
33
+ const outcome = await context.adapter.deleteScopeData(context.scopeTargetFor(alice));
34
+ expect(outcome.removedRecords).toBe(2);
35
+
36
+ const reopenedAlice = await context.adapter.open(context.configFor(alice));
37
+ expect(await reopenedAlice.collection('notes').count()).toBe(0);
38
+ expect(await reopenedAlice.collection('plain').count()).toBe(0);
39
+ await reopenedAlice.close();
40
+
41
+ const reopenedBob = await context.adapter.open(context.configFor(bob));
42
+ expect((await reopenedBob.collection('notes').get('x')).data.title).toBe('bob');
43
+ await reopenedBob.close();
44
+ });
45
+
46
+ it('reports success for a scope holding no data', async () => {
47
+ const outcome = await context.adapter.deleteScopeData(
48
+ context.scopeTargetFor({ kind: 'identity', id: 'never-used' }),
49
+ );
50
+ expect(outcome.removedRecords).toBe(0);
51
+ expect(outcome.failedRemovals).toEqual([]);
52
+ });
53
+
54
+ it('reports what it removed rather than a bare success', async () => {
55
+ const handle = await context.adapter.open(context.configFor(alice));
56
+ await handle.collection('notes').insert({ id: 'a', data: { title: 'x' } });
57
+ await handle.close();
58
+
59
+ const outcome = await context.adapter.deleteScopeData(context.scopeTargetFor(alice));
60
+ expect(outcome).toMatchObject({
61
+ removedRecords: 1,
62
+ removedArtifacts: [],
63
+ orphanedReferences: [],
64
+ failedRemovals: [],
65
+ });
66
+ });
67
+
68
+ it('makes the whole database behave as a first install after deleteDatabase', async () => {
69
+ const handle = await context.adapter.open(context.configFor(alice));
70
+ await handle.collection('notes').insert({ id: 'a', data: { title: 'x' } });
71
+ await handle.close();
72
+
73
+ await context.adapter.deleteDatabase({ name: 'conformance' });
74
+
75
+ const reopened = await context.adapter.open(context.configFor(alice));
76
+ expect(await reopened.collection('notes').count()).toBe(0);
77
+ await reopened.close();
78
+ });
79
+
80
+ it('recovers exactly one scope with resetScope', async () => {
81
+ const a = await context.adapter.open(context.configFor(alice));
82
+ await a.collection('notes').insert({ id: 'x', data: { title: 'alice' } });
83
+ await a.close();
84
+ const b = await context.adapter.open(context.configFor(bob));
85
+ await b.collection('notes').insert({ id: 'x', data: { title: 'bob' } });
86
+ await b.close();
87
+
88
+ await context.adapter.resetScope(context.scopeTargetFor(alice));
89
+
90
+ const reopenedAlice = await context.adapter.open(context.configFor(alice));
91
+ expect(await reopenedAlice.collection('notes').count()).toBe(0);
92
+ await reopenedAlice.close();
93
+
94
+ const reopenedBob = await context.adapter.open(context.configFor(bob));
95
+ expect(await reopenedBob.collection('notes').count()).toBe(1);
96
+ await reopenedBob.close();
97
+ });
98
+ });
99
+ }