@offlinejs/storage-memory 0.1.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.
@@ -0,0 +1,41 @@
1
+ import { IndexableStorageAdapter, EntityRecord, QueryOptions, IndexDefinition, TransactionStore, StorageMigration } from '@offlinejs/types';
2
+
3
+ interface MemoryStorageOptions {
4
+ name?: string;
5
+ seed?: Record<string, EntityRecord[]>;
6
+ }
7
+ declare class MemoryStorageAdapter implements IndexableStorageAdapter {
8
+ readonly name: string;
9
+ readonly contractVersion = 1;
10
+ readonly capabilities: {
11
+ readonly indexes: true;
12
+ readonly migrations: true;
13
+ readonly persistence: "ephemeral";
14
+ readonly transactions: "atomic";
15
+ };
16
+ private readonly records;
17
+ private readonly indexes;
18
+ /** collection → indexName → serializedValue → record ids */
19
+ private readonly secondary;
20
+ private readonly appliedMigrations;
21
+ constructor(options?: MemoryStorageOptions);
22
+ get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null>;
23
+ set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void>;
24
+ delete(collection: string, id: string): Promise<void>;
25
+ find<TRecord extends EntityRecord>(collection: string, query?: QueryOptions<TRecord>): Promise<TRecord[]>;
26
+ clear(collection?: string): Promise<void>;
27
+ createIndex<TRecord extends EntityRecord>(definition: IndexDefinition<TRecord>): Promise<void>;
28
+ dropIndex(collection: string, name: string): Promise<void>;
29
+ listIndexes(collection?: string): Promise<IndexDefinition[]>;
30
+ transaction<TValue>(scope: string[], run: (store: TransactionStore) => Promise<TValue>): Promise<TValue>;
31
+ migrate(migrations: StorageMigration[]): Promise<void>;
32
+ private findViaIndex;
33
+ private assertUniqueIndexes;
34
+ private indexRecord;
35
+ private unindexRecord;
36
+ private addToSecondary;
37
+ private ensureCollection;
38
+ }
39
+ declare const createMemoryStorage: (options?: MemoryStorageOptions) => MemoryStorageAdapter;
40
+
41
+ export { MemoryStorageAdapter, type MemoryStorageOptions, createMemoryStorage };
package/dist/index.js ADDED
@@ -0,0 +1,230 @@
1
+ import { STORAGE_ADAPTER_CONTRACT_VERSION } from '@offlinejs/types';
2
+ import { clone, applyQuery, findMatchingIndex, getEqualityFilterLookups, serializeCompoundIndexValue, readIndexFields } from '@offlinejs/utils';
3
+
4
+ // src/index.ts
5
+ var MemoryStorageAdapter = class {
6
+ name;
7
+ contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;
8
+ capabilities = {
9
+ indexes: true,
10
+ migrations: true,
11
+ persistence: "ephemeral",
12
+ transactions: "atomic"
13
+ };
14
+ records = /* @__PURE__ */ new Map();
15
+ indexes = /* @__PURE__ */ new Map();
16
+ /** collection → indexName → serializedValue → record ids */
17
+ secondary = /* @__PURE__ */ new Map();
18
+ appliedMigrations = /* @__PURE__ */ new Set();
19
+ constructor(options = {}) {
20
+ this.name = options.name ?? "memory";
21
+ for (const [collection, records] of Object.entries(options.seed ?? {})) {
22
+ this.records.set(collection, new Map(records.map((record) => [record.id, clone(record)])));
23
+ }
24
+ }
25
+ async get(collection, id) {
26
+ const record = this.records.get(collection)?.get(id);
27
+ return record ? clone(record) : null;
28
+ }
29
+ async set(collection, value) {
30
+ const previous = this.records.get(collection)?.get(value.id);
31
+ if (previous) {
32
+ this.unindexRecord(collection, previous);
33
+ }
34
+ this.assertUniqueIndexes(collection, value, previous?.id);
35
+ this.ensureCollection(collection).set(value.id, clone(value));
36
+ this.indexRecord(collection, value);
37
+ }
38
+ async delete(collection, id) {
39
+ const previous = this.records.get(collection)?.get(id);
40
+ if (previous) {
41
+ this.unindexRecord(collection, previous);
42
+ }
43
+ this.records.get(collection)?.delete(id);
44
+ }
45
+ async find(collection, query) {
46
+ const indexed = this.findViaIndex(collection, query);
47
+ const records = indexed ?? [...this.records.get(collection)?.values() ?? []].map((record) => clone(record));
48
+ return applyQuery(records, query);
49
+ }
50
+ async clear(collection) {
51
+ if (collection) {
52
+ this.records.delete(collection);
53
+ this.indexes.delete(collection);
54
+ this.secondary.delete(collection);
55
+ return;
56
+ }
57
+ this.records.clear();
58
+ this.indexes.clear();
59
+ this.secondary.clear();
60
+ }
61
+ async createIndex(definition) {
62
+ const normalized = clone(definition);
63
+ const collectionIndexes = this.indexes.get(definition.collection) ?? /* @__PURE__ */ new Map();
64
+ collectionIndexes.set(definition.name, normalized);
65
+ this.indexes.set(definition.collection, collectionIndexes);
66
+ const bucket = /* @__PURE__ */ new Map();
67
+ const collectionSecondary = this.secondary.get(definition.collection) ?? /* @__PURE__ */ new Map();
68
+ collectionSecondary.set(definition.name, bucket);
69
+ this.secondary.set(definition.collection, collectionSecondary);
70
+ for (const record of this.records.get(definition.collection)?.values() ?? []) {
71
+ this.assertUniqueIndexes(definition.collection, record);
72
+ this.addToSecondary(definition.collection, normalized, record);
73
+ }
74
+ }
75
+ async dropIndex(collection, name) {
76
+ this.indexes.get(collection)?.delete(name);
77
+ this.secondary.get(collection)?.delete(name);
78
+ }
79
+ async listIndexes(collection) {
80
+ if (collection) {
81
+ return [...this.indexes.get(collection)?.values() ?? []].map((index) => clone(index));
82
+ }
83
+ return [...this.indexes.values()].flatMap(
84
+ (indexes) => [...indexes.values()].map((index) => clone(index))
85
+ );
86
+ }
87
+ async transaction(scope, run) {
88
+ const snapshot = /* @__PURE__ */ new Map();
89
+ const indexSnapshot = /* @__PURE__ */ new Map();
90
+ const secondarySnapshot = /* @__PURE__ */ new Map();
91
+ for (const collection of scope) {
92
+ snapshot.set(collection, new Map(this.records.get(collection) ?? []));
93
+ indexSnapshot.set(
94
+ collection,
95
+ new Map(
96
+ [...this.indexes.get(collection)?.entries() ?? []].map(([name, definition]) => [
97
+ name,
98
+ clone(definition)
99
+ ])
100
+ )
101
+ );
102
+ secondarySnapshot.set(collection, cloneSecondary(this.secondary.get(collection)));
103
+ }
104
+ try {
105
+ return await run(this);
106
+ } catch (error) {
107
+ for (const collection of scope) {
108
+ const records = snapshot.get(collection);
109
+ if (records) {
110
+ this.records.set(collection, records);
111
+ } else {
112
+ this.records.delete(collection);
113
+ }
114
+ const indexes = indexSnapshot.get(collection);
115
+ if (indexes && indexes.size > 0) {
116
+ this.indexes.set(collection, indexes);
117
+ } else {
118
+ this.indexes.delete(collection);
119
+ }
120
+ const secondary = secondarySnapshot.get(collection);
121
+ if (secondary && secondary.size > 0) {
122
+ this.secondary.set(collection, secondary);
123
+ } else {
124
+ this.secondary.delete(collection);
125
+ }
126
+ }
127
+ throw error;
128
+ }
129
+ }
130
+ async migrate(migrations) {
131
+ for (const migration of migrations) {
132
+ if (this.appliedMigrations.has(migration.name)) {
133
+ continue;
134
+ }
135
+ await this.transaction(["__migrations"], async (store) => {
136
+ await migration.up(store);
137
+ this.appliedMigrations.add(migration.name);
138
+ });
139
+ }
140
+ }
141
+ findViaIndex(collection, query) {
142
+ const definitions = [...this.indexes.get(collection)?.values() ?? []];
143
+ const match = findMatchingIndex(definitions, getEqualityFilterLookups(query?.filters));
144
+ if (!match) {
145
+ return null;
146
+ }
147
+ const valueKey = serializeCompoundIndexValue(match.values);
148
+ const ids = this.secondary.get(collection)?.get(match.index.name)?.get(valueKey);
149
+ if (!ids) {
150
+ return [];
151
+ }
152
+ const records = [];
153
+ for (const id of ids) {
154
+ const record = this.records.get(collection)?.get(id);
155
+ if (record) {
156
+ records.push(clone(record));
157
+ }
158
+ }
159
+ return records;
160
+ }
161
+ assertUniqueIndexes(collection, record, ignoreId) {
162
+ for (const definition of this.indexes.get(collection)?.values() ?? []) {
163
+ if (!definition.unique) {
164
+ continue;
165
+ }
166
+ const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));
167
+ const ids = this.secondary.get(collection)?.get(definition.name)?.get(valueKey);
168
+ if (!ids) {
169
+ continue;
170
+ }
171
+ for (const id of ids) {
172
+ if (id !== record.id && id !== ignoreId) {
173
+ throw new Error(
174
+ `Unique index "${definition.name}" violated for ${collection}.${String(definition.fields[0])}`
175
+ );
176
+ }
177
+ }
178
+ }
179
+ }
180
+ indexRecord(collection, record) {
181
+ for (const definition of this.indexes.get(collection)?.values() ?? []) {
182
+ this.addToSecondary(collection, definition, record);
183
+ }
184
+ }
185
+ unindexRecord(collection, record) {
186
+ for (const definition of this.indexes.get(collection)?.values() ?? []) {
187
+ const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));
188
+ const bucket = this.secondary.get(collection)?.get(definition.name)?.get(valueKey);
189
+ bucket?.delete(record.id);
190
+ if (bucket && bucket.size === 0) {
191
+ this.secondary.get(collection)?.get(definition.name)?.delete(valueKey);
192
+ }
193
+ }
194
+ }
195
+ addToSecondary(collection, definition, record) {
196
+ const collectionSecondary = this.secondary.get(collection) ?? /* @__PURE__ */ new Map();
197
+ const indexBucket = collectionSecondary.get(definition.name) ?? /* @__PURE__ */ new Map();
198
+ const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));
199
+ const ids = indexBucket.get(valueKey) ?? /* @__PURE__ */ new Set();
200
+ ids.add(record.id);
201
+ indexBucket.set(valueKey, ids);
202
+ collectionSecondary.set(definition.name, indexBucket);
203
+ this.secondary.set(collection, collectionSecondary);
204
+ }
205
+ ensureCollection(collection) {
206
+ const existing = this.records.get(collection);
207
+ if (existing) {
208
+ return existing;
209
+ }
210
+ const records = /* @__PURE__ */ new Map();
211
+ this.records.set(collection, records);
212
+ return records;
213
+ }
214
+ };
215
+ var cloneSecondary = (source) => {
216
+ const cloned = /* @__PURE__ */ new Map();
217
+ for (const [indexName, values] of source ?? []) {
218
+ const valueMap = /* @__PURE__ */ new Map();
219
+ for (const [valueKey, ids] of values) {
220
+ valueMap.set(valueKey, new Set(ids));
221
+ }
222
+ cloned.set(indexName, valueMap);
223
+ }
224
+ return cloned;
225
+ };
226
+ var createMemoryStorage = (options) => new MemoryStorageAdapter(options);
227
+
228
+ export { MemoryStorageAdapter, createMemoryStorage };
229
+ //# sourceMappingURL=index.js.map
230
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n STORAGE_ADAPTER_CONTRACT_VERSION,\n type EntityRecord,\n type IndexDefinition,\n type IndexableStorageAdapter,\n type QueryOptions,\n type StorageMigration,\n type TransactionStore\n} from \"@offlinejs/types\";\nimport {\n applyQuery,\n clone,\n findMatchingIndex,\n getEqualityFilterLookups,\n readIndexFields,\n serializeCompoundIndexValue\n} from \"@offlinejs/utils\";\n\nexport interface MemoryStorageOptions {\n name?: string;\n seed?: Record<string, EntityRecord[]>;\n}\n\nexport class MemoryStorageAdapter implements IndexableStorageAdapter {\n readonly name: string;\n readonly contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;\n readonly capabilities = {\n indexes: true,\n migrations: true,\n persistence: \"ephemeral\",\n transactions: \"atomic\"\n } as const;\n\n private readonly records = new Map<string, Map<string, EntityRecord>>();\n private readonly indexes = new Map<string, Map<string, IndexDefinition>>();\n /** collection → indexName → serializedValue → record ids */\n private readonly secondary = new Map<string, Map<string, Map<string, Set<string>>>>();\n private readonly appliedMigrations = new Set<string>();\n\n constructor(options: MemoryStorageOptions = {}) {\n this.name = options.name ?? \"memory\";\n\n for (const [collection, records] of Object.entries(options.seed ?? {})) {\n this.records.set(collection, new Map(records.map((record) => [record.id, clone(record)])));\n }\n }\n\n async get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null> {\n const record = this.records.get(collection)?.get(id);\n return record ? clone(record as TRecord) : null;\n }\n\n async set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void> {\n const previous = this.records.get(collection)?.get(value.id);\n if (previous) {\n this.unindexRecord(collection, previous);\n }\n\n this.assertUniqueIndexes(collection, value, previous?.id);\n this.ensureCollection(collection).set(value.id, clone(value));\n this.indexRecord(collection, value);\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const previous = this.records.get(collection)?.get(id);\n if (previous) {\n this.unindexRecord(collection, previous);\n }\n this.records.get(collection)?.delete(id);\n }\n\n async find<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[]> {\n const indexed = this.findViaIndex<TRecord>(collection, query);\n const records =\n indexed ??\n [...(this.records.get(collection)?.values() ?? [])].map((record) => clone(record as TRecord));\n\n return applyQuery(records, query);\n }\n\n async clear(collection?: string): Promise<void> {\n if (collection) {\n this.records.delete(collection);\n this.indexes.delete(collection);\n this.secondary.delete(collection);\n return;\n }\n\n this.records.clear();\n this.indexes.clear();\n this.secondary.clear();\n }\n\n async createIndex<TRecord extends EntityRecord>(\n definition: IndexDefinition<TRecord>\n ): Promise<void> {\n const normalized = clone(definition as IndexDefinition);\n const collectionIndexes = this.indexes.get(definition.collection) ?? new Map();\n collectionIndexes.set(definition.name, normalized);\n this.indexes.set(definition.collection, collectionIndexes);\n\n const bucket = new Map<string, Set<string>>();\n const collectionSecondary = this.secondary.get(definition.collection) ?? new Map();\n collectionSecondary.set(definition.name, bucket);\n this.secondary.set(definition.collection, collectionSecondary);\n\n for (const record of this.records.get(definition.collection)?.values() ?? []) {\n this.assertUniqueIndexes(definition.collection, record);\n this.addToSecondary(definition.collection, normalized, record);\n }\n }\n\n async dropIndex(collection: string, name: string): Promise<void> {\n this.indexes.get(collection)?.delete(name);\n this.secondary.get(collection)?.delete(name);\n }\n\n async listIndexes(collection?: string): Promise<IndexDefinition[]> {\n if (collection) {\n return [...(this.indexes.get(collection)?.values() ?? [])].map((index) => clone(index));\n }\n\n return [...this.indexes.values()].flatMap((indexes) =>\n [...indexes.values()].map((index) => clone(index))\n );\n }\n\n async transaction<TValue>(\n scope: string[],\n run: (store: TransactionStore) => Promise<TValue>\n ): Promise<TValue> {\n const snapshot = new Map<string, Map<string, EntityRecord>>();\n const indexSnapshot = new Map<string, Map<string, IndexDefinition>>();\n const secondarySnapshot = new Map<string, Map<string, Map<string, Set<string>>>>();\n\n for (const collection of scope) {\n snapshot.set(collection, new Map(this.records.get(collection) ?? []));\n indexSnapshot.set(\n collection,\n new Map(\n [...(this.indexes.get(collection)?.entries() ?? [])].map(([name, definition]) => [\n name,\n clone(definition)\n ])\n )\n );\n secondarySnapshot.set(collection, cloneSecondary(this.secondary.get(collection)));\n }\n\n try {\n return await run(this);\n } catch (error) {\n for (const collection of scope) {\n const records = snapshot.get(collection);\n if (records) {\n this.records.set(collection, records);\n } else {\n this.records.delete(collection);\n }\n\n const indexes = indexSnapshot.get(collection);\n if (indexes && indexes.size > 0) {\n this.indexes.set(collection, indexes);\n } else {\n this.indexes.delete(collection);\n }\n\n const secondary = secondarySnapshot.get(collection);\n if (secondary && secondary.size > 0) {\n this.secondary.set(collection, secondary);\n } else {\n this.secondary.delete(collection);\n }\n }\n\n throw error;\n }\n }\n\n async migrate(migrations: StorageMigration[]): Promise<void> {\n for (const migration of migrations) {\n if (this.appliedMigrations.has(migration.name)) {\n continue;\n }\n\n await this.transaction([\"__migrations\"], async (store) => {\n await migration.up(store);\n this.appliedMigrations.add(migration.name);\n });\n }\n }\n\n private findViaIndex<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): TRecord[] | null {\n const definitions = [...(this.indexes.get(collection)?.values() ?? [])];\n const match = findMatchingIndex(definitions, getEqualityFilterLookups(query?.filters));\n\n if (!match) {\n return null;\n }\n\n const valueKey = serializeCompoundIndexValue(match.values);\n const ids = this.secondary.get(collection)?.get(match.index.name)?.get(valueKey);\n if (!ids) {\n return [];\n }\n\n const records: TRecord[] = [];\n for (const id of ids) {\n const record = this.records.get(collection)?.get(id);\n if (record) {\n records.push(clone(record as TRecord));\n }\n }\n\n return records;\n }\n\n private assertUniqueIndexes(\n collection: string,\n record: EntityRecord,\n ignoreId?: string\n ): void {\n for (const definition of this.indexes.get(collection)?.values() ?? []) {\n if (!definition.unique) {\n continue;\n }\n\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const ids = this.secondary.get(collection)?.get(definition.name)?.get(valueKey);\n\n if (!ids) {\n continue;\n }\n\n for (const id of ids) {\n if (id !== record.id && id !== ignoreId) {\n throw new Error(\n `Unique index \"${definition.name}\" violated for ${collection}.${String(definition.fields[0])}`\n );\n }\n }\n }\n }\n\n private indexRecord(collection: string, record: EntityRecord): void {\n for (const definition of this.indexes.get(collection)?.values() ?? []) {\n this.addToSecondary(collection, definition, record);\n }\n }\n\n private unindexRecord(collection: string, record: EntityRecord): void {\n for (const definition of this.indexes.get(collection)?.values() ?? []) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const bucket = this.secondary.get(collection)?.get(definition.name)?.get(valueKey);\n bucket?.delete(record.id);\n if (bucket && bucket.size === 0) {\n this.secondary.get(collection)?.get(definition.name)?.delete(valueKey);\n }\n }\n }\n\n private addToSecondary(\n collection: string,\n definition: IndexDefinition,\n record: EntityRecord\n ): void {\n const collectionSecondary = this.secondary.get(collection) ?? new Map();\n const indexBucket = collectionSecondary.get(definition.name) ?? new Map<string, Set<string>>();\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const ids = indexBucket.get(valueKey) ?? new Set<string>();\n ids.add(record.id);\n indexBucket.set(valueKey, ids);\n collectionSecondary.set(definition.name, indexBucket);\n this.secondary.set(collection, collectionSecondary);\n }\n\n private ensureCollection(collection: string): Map<string, EntityRecord> {\n const existing = this.records.get(collection);\n\n if (existing) {\n return existing;\n }\n\n const records = new Map<string, EntityRecord>();\n this.records.set(collection, records);\n return records;\n }\n}\n\nconst cloneSecondary = (\n source?: Map<string, Map<string, Set<string>>>\n): Map<string, Map<string, Set<string>>> => {\n const cloned = new Map<string, Map<string, Set<string>>>();\n\n for (const [indexName, values] of source ?? []) {\n const valueMap = new Map<string, Set<string>>();\n for (const [valueKey, ids] of values) {\n valueMap.set(valueKey, new Set(ids));\n }\n cloned.set(indexName, valueMap);\n }\n\n return cloned;\n};\n\nexport const createMemoryStorage = (options?: MemoryStorageOptions): MemoryStorageAdapter =>\n new MemoryStorageAdapter(options);\n"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@offlinejs/storage-memory",
3
+ "version": "0.1.0",
4
+ "description": "In-memory OfflineJS storage adapter for tests, Node.js, and ephemeral sessions.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "dependencies": {
17
+ "@offlinejs/types": "0.1.0",
18
+ "@offlinejs/utils": "0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsup": "latest",
22
+ "typescript": "latest"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "typecheck": "tsc --noEmit"
31
+ }
32
+ }