@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.
- package/CHANGELOG.md +122 -52
- package/README.md +663 -661
- 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 -238
- package/src/adapter/index.ts +64 -68
- package/src/adapter/lifecycle.ts +517 -444
- package/src/adapter/transaction.ts +71 -65
- package/src/artifacts/availability.ts +67 -69
- package/src/artifacts/cascade.ts +114 -78
- package/src/artifacts/paths.ts +29 -29
- package/src/artifacts/reconcile.ts +63 -63
- package/src/artifacts/store.ts +334 -323
- package/src/contract/adapter.ts +76 -78
- package/src/contract/attachment.ts +75 -72
- 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 -40
- package/src/contract/query.ts +81 -81
- package/src/contract/record.ts +46 -42
- package/src/contract/schema.ts +36 -36
- package/src/contract/scope.ts +36 -36
- package/src/contract/transaction.ts +52 -46
- package/src/crypto/chunked-cipher.ts +153 -144
- package/src/crypto/derive.ts +64 -63
- 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 -0
- package/src/platform/fs.ts +89 -89
- package/src/platform/location.ts +135 -135
- package/src/query/budgets.ts +54 -57
- package/src/query/compare.ts +91 -91
- package/src/query/cursor.ts +100 -99
- 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 -232
- package/src/sqlite/catalog.ts +105 -108
- package/src/sqlite/connection.ts +227 -205
- package/src/sqlite/index-coverage.ts +54 -53
- package/src/sqlite/native.ts +83 -83
- package/src/sqlite/row-codec.ts +217 -201
- package/src/sqlite/sql-builder.ts +244 -244
- package/src/sqlite/transaction-queue.ts +87 -0
- 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 -388
- 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
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
import { DiagnosticCodes } from '../../contract/errors';
|
|
2
|
-
import { isCode } from '../../diagnostics/errors';
|
|
3
|
-
import type { ConformanceContext } from './index';
|
|
4
|
-
|
|
5
|
-
/** US4: all-or-nothing writes. */
|
|
6
|
-
export function registerTransactionConformance(context: ConformanceContext): void {
|
|
7
|
-
describe('conformance: transactions', () => {
|
|
8
|
-
beforeEach(() => context.reset());
|
|
9
|
-
|
|
10
|
-
const scope = { kind: 'guest' } as const;
|
|
11
|
-
|
|
12
|
-
it('commits every write together', async () => {
|
|
13
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
14
|
-
await handle.transaction(async tx => {
|
|
15
|
-
await tx.collection('notes').insert({ id: 'a', data: { title: 'a' } });
|
|
16
|
-
await tx.collection('plain').insert({ id: 'b', data: { label: 'b' } });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
expect(await handle.collection('notes').find('a')).not.toBeNull();
|
|
20
|
-
expect(await handle.collection('plain').find('b')).not.toBeNull();
|
|
21
|
-
await handle.close();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('rolls back every write when one fails', async () => {
|
|
25
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
26
|
-
await handle.collection('notes').insert({ id: 'existing', data: { title: 'kept' } });
|
|
27
|
-
|
|
28
|
-
let thrown: unknown;
|
|
29
|
-
try {
|
|
30
|
-
await handle.transaction(async tx => {
|
|
31
|
-
await tx.collection('notes').insert({ id: 'new', data: { title: 'new' } });
|
|
32
|
-
// Conflicts with `existing`, so the whole transaction unwinds.
|
|
33
|
-
await tx.collection('notes').insert({ id: 'existing', data: { title: 'clash' } });
|
|
34
|
-
});
|
|
35
|
-
} catch (error) {
|
|
36
|
-
thrown = error;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
expect(isCode(thrown, DiagnosticCodes.RECORD_CONFLICT)).toBe(true);
|
|
40
|
-
expect(await handle.collection('notes').find('new')).toBeNull();
|
|
41
|
-
expect((await handle.collection('notes').get('existing')).data.title).toBe('kept');
|
|
42
|
-
await handle.close();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it.each([0, 1, 2, 3])('rolls back cleanly when step %p fails', async failAt => {
|
|
46
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
47
|
-
const before = await handle.collection('notes').count();
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
await handle.transaction(async tx => {
|
|
51
|
-
for (let step = 0; step < 4; step += 1) {
|
|
52
|
-
if (step === failAt) {
|
|
53
|
-
throw new Error('injected');
|
|
54
|
-
}
|
|
55
|
-
await tx.collection('notes').insert({ id: `s${step}`, data: { score: step } });
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
} catch {
|
|
59
|
-
// Expected.
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
expect(await handle.collection('notes').count()).toBe(before);
|
|
63
|
-
await handle.close();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('discards everything on an explicit abort', async () => {
|
|
67
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
68
|
-
await handle.transaction(async tx => {
|
|
69
|
-
await tx.collection('notes').insert({ id: 'a', data: { title: 'a' } });
|
|
70
|
-
await tx.abort();
|
|
71
|
-
});
|
|
72
|
-
expect(await handle.collection('notes').find('a')).toBeNull();
|
|
73
|
-
await handle.close();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('sees its own uncommitted writes inside the transaction', async () => {
|
|
77
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
78
|
-
await handle.transaction(async tx => {
|
|
79
|
-
const notes = tx.collection('notes');
|
|
80
|
-
await notes.insert({ id: 'a', data: { title: 'inside' } });
|
|
81
|
-
expect((await notes.get('a')).data.title).toBe('inside');
|
|
82
|
-
});
|
|
83
|
-
await handle.close();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('rejects use after the transaction settles', async () => {
|
|
87
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
88
|
-
let thrown: unknown;
|
|
89
|
-
await handle.transaction(async tx => {
|
|
90
|
-
await tx.abort();
|
|
91
|
-
try {
|
|
92
|
-
tx.collection('notes');
|
|
93
|
-
} catch (error) {
|
|
94
|
-
thrown = error;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
expect(isCode(thrown, DiagnosticCodes.TRANSACTION_STATE)).toBe(true);
|
|
98
|
-
await handle.close();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('applies a batch as one unit', async () => {
|
|
102
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
103
|
-
const outcome = await handle.batch([
|
|
104
|
-
{ type: 'insert', collection: 'notes', record: { id: 'a', data: { score: 1 } } },
|
|
105
|
-
{ type: 'insert', collection: 'notes', record: { id: 'b', data: { score: 2 } } },
|
|
106
|
-
{ type: 'upsert', collection: 'plain', record: { id: 'c', data: { label: 'c' } } },
|
|
107
|
-
]);
|
|
108
|
-
expect(outcome.applied).toBe(3);
|
|
109
|
-
expect(await handle.collection('notes').count()).toBe(2);
|
|
110
|
-
await handle.close();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('rolls back the whole batch when one operation fails', async () => {
|
|
114
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
115
|
-
await handle.collection('notes').insert({ id: 'a', data: { score: 0 } });
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
await handle.batch([
|
|
119
|
-
{ type: 'insert', collection: 'notes', record: { id: 'b', data: { score: 1 } } },
|
|
120
|
-
{ type: 'insert', collection: 'notes', record: { id: 'a', data: { score: 9 } } },
|
|
121
|
-
]);
|
|
122
|
-
} catch {
|
|
123
|
-
// Expected.
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
expect(await handle.collection('notes').find('b')).toBeNull();
|
|
127
|
-
expect((await handle.collection('notes').get('a')).data.score).toBe(0);
|
|
128
|
-
await handle.close();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('rejects a batch over the operation budget', async () => {
|
|
132
|
-
const handle = await context.adapter.open(context.configFor(scope));
|
|
133
|
-
const operations = Array.from({ length: 1001 }, (_, i) => ({
|
|
134
|
-
type: 'upsert' as const,
|
|
135
|
-
collection: 'notes',
|
|
136
|
-
record: { id: `x${i}`, data: { score: i } },
|
|
137
|
-
}));
|
|
138
|
-
|
|
139
|
-
let thrown: unknown;
|
|
140
|
-
try {
|
|
141
|
-
await handle.batch(operations);
|
|
142
|
-
} catch (error) {
|
|
143
|
-
thrown = error;
|
|
144
|
-
}
|
|
145
|
-
expect(isCode(thrown, DiagnosticCodes.BATCH_TOO_LARGE)).toBe(true);
|
|
146
|
-
await handle.close();
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
1
|
+
import { DiagnosticCodes } from '../../contract/errors';
|
|
2
|
+
import { isCode } from '../../diagnostics/errors';
|
|
3
|
+
import type { ConformanceContext } from './index';
|
|
4
|
+
|
|
5
|
+
/** US4: all-or-nothing writes. */
|
|
6
|
+
export function registerTransactionConformance(context: ConformanceContext): void {
|
|
7
|
+
describe('conformance: transactions', () => {
|
|
8
|
+
beforeEach(() => context.reset());
|
|
9
|
+
|
|
10
|
+
const scope = { kind: 'guest' } as const;
|
|
11
|
+
|
|
12
|
+
it('commits every write together', async () => {
|
|
13
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
14
|
+
await handle.transaction(async tx => {
|
|
15
|
+
await tx.collection('notes').insert({ id: 'a', data: { title: 'a' } });
|
|
16
|
+
await tx.collection('plain').insert({ id: 'b', data: { label: 'b' } });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(await handle.collection('notes').find('a')).not.toBeNull();
|
|
20
|
+
expect(await handle.collection('plain').find('b')).not.toBeNull();
|
|
21
|
+
await handle.close();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('rolls back every write when one fails', async () => {
|
|
25
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
26
|
+
await handle.collection('notes').insert({ id: 'existing', data: { title: 'kept' } });
|
|
27
|
+
|
|
28
|
+
let thrown: unknown;
|
|
29
|
+
try {
|
|
30
|
+
await handle.transaction(async tx => {
|
|
31
|
+
await tx.collection('notes').insert({ id: 'new', data: { title: 'new' } });
|
|
32
|
+
// Conflicts with `existing`, so the whole transaction unwinds.
|
|
33
|
+
await tx.collection('notes').insert({ id: 'existing', data: { title: 'clash' } });
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
thrown = error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(isCode(thrown, DiagnosticCodes.RECORD_CONFLICT)).toBe(true);
|
|
40
|
+
expect(await handle.collection('notes').find('new')).toBeNull();
|
|
41
|
+
expect((await handle.collection('notes').get('existing')).data.title).toBe('kept');
|
|
42
|
+
await handle.close();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it.each([0, 1, 2, 3])('rolls back cleanly when step %p fails', async failAt => {
|
|
46
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
47
|
+
const before = await handle.collection('notes').count();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await handle.transaction(async tx => {
|
|
51
|
+
for (let step = 0; step < 4; step += 1) {
|
|
52
|
+
if (step === failAt) {
|
|
53
|
+
throw new Error('injected');
|
|
54
|
+
}
|
|
55
|
+
await tx.collection('notes').insert({ id: `s${step}`, data: { score: step } });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
// Expected.
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
expect(await handle.collection('notes').count()).toBe(before);
|
|
63
|
+
await handle.close();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('discards everything on an explicit abort', async () => {
|
|
67
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
68
|
+
await handle.transaction(async tx => {
|
|
69
|
+
await tx.collection('notes').insert({ id: 'a', data: { title: 'a' } });
|
|
70
|
+
await tx.abort();
|
|
71
|
+
});
|
|
72
|
+
expect(await handle.collection('notes').find('a')).toBeNull();
|
|
73
|
+
await handle.close();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('sees its own uncommitted writes inside the transaction', async () => {
|
|
77
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
78
|
+
await handle.transaction(async tx => {
|
|
79
|
+
const notes = tx.collection('notes');
|
|
80
|
+
await notes.insert({ id: 'a', data: { title: 'inside' } });
|
|
81
|
+
expect((await notes.get('a')).data.title).toBe('inside');
|
|
82
|
+
});
|
|
83
|
+
await handle.close();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('rejects use after the transaction settles', async () => {
|
|
87
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
88
|
+
let thrown: unknown;
|
|
89
|
+
await handle.transaction(async tx => {
|
|
90
|
+
await tx.abort();
|
|
91
|
+
try {
|
|
92
|
+
tx.collection('notes');
|
|
93
|
+
} catch (error) {
|
|
94
|
+
thrown = error;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
expect(isCode(thrown, DiagnosticCodes.TRANSACTION_STATE)).toBe(true);
|
|
98
|
+
await handle.close();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('applies a batch as one unit', async () => {
|
|
102
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
103
|
+
const outcome = await handle.batch([
|
|
104
|
+
{ type: 'insert', collection: 'notes', record: { id: 'a', data: { score: 1 } } },
|
|
105
|
+
{ type: 'insert', collection: 'notes', record: { id: 'b', data: { score: 2 } } },
|
|
106
|
+
{ type: 'upsert', collection: 'plain', record: { id: 'c', data: { label: 'c' } } },
|
|
107
|
+
]);
|
|
108
|
+
expect(outcome.applied).toBe(3);
|
|
109
|
+
expect(await handle.collection('notes').count()).toBe(2);
|
|
110
|
+
await handle.close();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('rolls back the whole batch when one operation fails', async () => {
|
|
114
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
115
|
+
await handle.collection('notes').insert({ id: 'a', data: { score: 0 } });
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await handle.batch([
|
|
119
|
+
{ type: 'insert', collection: 'notes', record: { id: 'b', data: { score: 1 } } },
|
|
120
|
+
{ type: 'insert', collection: 'notes', record: { id: 'a', data: { score: 9 } } },
|
|
121
|
+
]);
|
|
122
|
+
} catch {
|
|
123
|
+
// Expected.
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
expect(await handle.collection('notes').find('b')).toBeNull();
|
|
127
|
+
expect((await handle.collection('notes').get('a')).data.score).toBe(0);
|
|
128
|
+
await handle.close();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('rejects a batch over the operation budget', async () => {
|
|
132
|
+
const handle = await context.adapter.open(context.configFor(scope));
|
|
133
|
+
const operations = Array.from({ length: 1001 }, (_, i) => ({
|
|
134
|
+
type: 'upsert' as const,
|
|
135
|
+
collection: 'notes',
|
|
136
|
+
record: { id: `x${i}`, data: { score: i } },
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
let thrown: unknown;
|
|
140
|
+
try {
|
|
141
|
+
await handle.batch(operations);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
thrown = error;
|
|
144
|
+
}
|
|
145
|
+
expect(isCode(thrown, DiagnosticCodes.BATCH_TOO_LARGE)).toBe(true);
|
|
146
|
+
await handle.close();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
package/src/testing/faults.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fault injection, exposed only through the `./testing` entrypoint.
|
|
3
|
-
*
|
|
4
|
-
* Produces the same typed errors the on-device implementation produces, so a
|
|
5
|
-
* consumer can test its recovery paths without a device that is actually out
|
|
6
|
-
* of space or actually corrupt (FR-095). The registry itself lives in
|
|
7
|
-
* `diagnostics/faults.ts`, because the *checks* have to run in production code
|
|
8
|
-
* for the demo app's panel to exercise the real adapter — only the ability to
|
|
9
|
-
* arm one is kept behind this entrypoint.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
activeFaults,
|
|
14
|
-
clearFaults,
|
|
15
|
-
injectFault,
|
|
16
|
-
throwIfFaulted,
|
|
17
|
-
type FaultPoint,
|
|
18
|
-
type FaultSpec,
|
|
19
|
-
} from '../diagnostics/faults';
|
|
20
|
-
|
|
21
|
-
export { DiagnosticCodes } from '../contract/errors';
|
|
1
|
+
/**
|
|
2
|
+
* Fault injection, exposed only through the `./testing` entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* Produces the same typed errors the on-device implementation produces, so a
|
|
5
|
+
* consumer can test its recovery paths without a device that is actually out
|
|
6
|
+
* of space or actually corrupt (FR-095). The registry itself lives in
|
|
7
|
+
* `diagnostics/faults.ts`, because the *checks* have to run in production code
|
|
8
|
+
* for the demo app's panel to exercise the real adapter — only the ability to
|
|
9
|
+
* arm one is kept behind this entrypoint.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
activeFaults,
|
|
14
|
+
clearFaults,
|
|
15
|
+
injectFault,
|
|
16
|
+
throwIfFaulted,
|
|
17
|
+
type FaultPoint,
|
|
18
|
+
type FaultSpec,
|
|
19
|
+
} from '../diagnostics/faults';
|
|
20
|
+
|
|
21
|
+
export { DiagnosticCodes } from '../contract/errors';
|
package/src/testing/index.ts
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@chipmobilesdk/rn-local-db/testing`
|
|
3
|
-
*
|
|
4
|
-
* Test-only surface, kept behind its own entrypoint so the in-memory engine
|
|
5
|
-
* never reaches a consuming app's production bundle (research.md R10).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { createMemoryAdapter, type MemoryAdapterOptions } from './memory-adapter';
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type ConformanceHooks,
|
|
19
|
-
type ConformanceOptions,
|
|
20
|
-
type ConformanceSection,
|
|
21
|
-
} from './conformance';
|
|
1
|
+
/**
|
|
2
|
+
* `@chipmobilesdk/rn-local-db/testing`
|
|
3
|
+
*
|
|
4
|
+
* Test-only surface, kept behind its own entrypoint so the in-memory engine
|
|
5
|
+
* never reaches a consuming app's production bundle (research.md R10).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { createMemoryAdapter, type MemoryAdapterOptions } from './memory-adapter';
|
|
9
|
+
export { memoryConformanceHooks, resetMemoryAdapter, seedRecords } from './memory-adapter/reset';
|
|
10
|
+
export { clearFaults, injectFault, type FaultPoint, type FaultSpec } from './faults';
|
|
11
|
+
export {
|
|
12
|
+
runConformanceSuite,
|
|
13
|
+
type ConformanceContext,
|
|
14
|
+
type ConformanceHooks,
|
|
15
|
+
type ConformanceOptions,
|
|
16
|
+
type ConformanceSection,
|
|
17
|
+
} from './conformance';
|