@puzzle-ui/storage 0.0.0-stage → 0.5.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.
- package/README.md +118 -2
- package/dist/LICENSE +21 -0
- package/dist/errors/storage.errors.cjs +55 -0
- package/dist/errors/storage.errors.cjs.map +1 -0
- package/dist/errors/storage.errors.d.cts +27 -0
- package/dist/errors/storage.errors.d.ts +27 -0
- package/dist/errors/storage.errors.js +50 -0
- package/dist/errors/storage.errors.js.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -0
- package/dist/types/adapter.types.d.cts +68 -0
- package/dist/types/adapter.types.d.ts +68 -0
- package/dist/types/capabilities.types.cjs +14 -0
- package/dist/types/capabilities.types.cjs.map +1 -0
- package/dist/types/capabilities.types.d.cts +16 -0
- package/dist/types/capabilities.types.d.ts +16 -0
- package/dist/types/capabilities.types.js +14 -0
- package/dist/types/capabilities.types.js.map +1 -0
- package/dist/types/collection.types.d.cts +25 -0
- package/dist/types/collection.types.d.ts +25 -0
- package/dist/types/migration.types.d.cts +53 -0
- package/dist/types/migration.types.d.ts +53 -0
- package/dist/types/query.types.d.cts +55 -0
- package/dist/types/query.types.d.ts +55 -0
- package/dist/types/schema.types.d.cts +16 -0
- package/dist/types/schema.types.d.ts +16 -0
- package/dist/utils/query.utils.cjs +44 -0
- package/dist/utils/query.utils.cjs.map +1 -0
- package/dist/utils/query.utils.d.cts +4 -0
- package/dist/utils/query.utils.d.ts +4 -0
- package/dist/utils/query.utils.js +43 -0
- package/dist/utils/query.utils.js.map +1 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,119 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @puzzle-ui/storage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vendor-neutral storage contracts for Puzzle.
|
|
4
|
+
|
|
5
|
+
This package defines the persistence API used by Puzzle Auth and other
|
|
6
|
+
packages. It does not depend on a database vendor, database SDK, framework,
|
|
7
|
+
or runtime-specific implementation.
|
|
8
|
+
|
|
9
|
+
## Responsibilities
|
|
10
|
+
|
|
11
|
+
- Typed collection and record contracts.
|
|
12
|
+
- Query AST and cursor pagination contracts.
|
|
13
|
+
- CRUD, upsert and conditional write contracts.
|
|
14
|
+
- Optimistic concurrency through record revisions.
|
|
15
|
+
- Capability discovery.
|
|
16
|
+
- Atomic batch contract.
|
|
17
|
+
- Collection schemas and index definitions.
|
|
18
|
+
- Migration contracts and migration status.
|
|
19
|
+
- Reusable adapter conformance requirements.
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
Database-specific implementations belong in separate packages:
|
|
24
|
+
|
|
25
|
+
- `@puzzle-ui/storage-mongodb`
|
|
26
|
+
- `@puzzle-ui/storage-d1`
|
|
27
|
+
|
|
28
|
+
Neither adapter may leak its database SDK or query language through this
|
|
29
|
+
package.
|
|
30
|
+
|
|
31
|
+
## Records
|
|
32
|
+
|
|
33
|
+
Every persisted record has:
|
|
34
|
+
|
|
35
|
+
- `id`
|
|
36
|
+
- `createdAt`
|
|
37
|
+
- `updatedAt`
|
|
38
|
+
- `revision`
|
|
39
|
+
|
|
40
|
+
The revision is incremented by successful updates. Adapters must use it
|
|
41
|
+
for optimistic concurrency when a caller supplies `expectedRevision`.
|
|
42
|
+
|
|
43
|
+
## Atomicity
|
|
44
|
+
|
|
45
|
+
An adapter may advertise `atomicBatch: true` only when it guarantees
|
|
46
|
+
all-or-nothing execution.
|
|
47
|
+
|
|
48
|
+
If a batch fails, all changes made by that batch must be rolled back.
|
|
49
|
+
Adapters without this guarantee must reject `atomicBatch` calls with
|
|
50
|
+
`StorageAtomicityError`.
|
|
51
|
+
|
|
52
|
+
Security-critical operations must fail closed when the required
|
|
53
|
+
capability is unavailable.
|
|
54
|
+
|
|
55
|
+
## Query contract
|
|
56
|
+
|
|
57
|
+
Queries use a vendor-neutral AST:
|
|
58
|
+
|
|
59
|
+
- `eq`, `ne`
|
|
60
|
+
- `in`, `notIn`
|
|
61
|
+
- `gt`, `gte`, `lt`, `lte`
|
|
62
|
+
- `exists`
|
|
63
|
+
- nested `and` and `or`
|
|
64
|
+
- ordering
|
|
65
|
+
- offset and limit
|
|
66
|
+
- cursor pagination
|
|
67
|
+
|
|
68
|
+
Adapters are responsible for translating this AST into their native
|
|
69
|
+
query mechanism without changing its semantics.
|
|
70
|
+
|
|
71
|
+
## Schemas and indexes
|
|
72
|
+
|
|
73
|
+
Collection schemas validate records at the storage boundary.
|
|
74
|
+
|
|
75
|
+
Indexes can declare:
|
|
76
|
+
|
|
77
|
+
- Multiple indexed fields.
|
|
78
|
+
- Uniqueness.
|
|
79
|
+
- TTL expiration.
|
|
80
|
+
|
|
81
|
+
An adapter must report unsupported index capabilities instead of
|
|
82
|
+
silently ignoring requested guarantees.
|
|
83
|
+
|
|
84
|
+
## Migrations
|
|
85
|
+
|
|
86
|
+
Migrations are explicit and versioned. They are never run implicitly
|
|
87
|
+
during ordinary reads or writes.
|
|
88
|
+
|
|
89
|
+
A migration adapter must provide durable migration status and prevent
|
|
90
|
+
concurrent migration runners from applying the same migration twice.
|
|
91
|
+
|
|
92
|
+
## Adapter requirements
|
|
93
|
+
|
|
94
|
+
Every production adapter must pass the same conformance suite.
|
|
95
|
+
|
|
96
|
+
At minimum, conformance must verify:
|
|
97
|
+
|
|
98
|
+
- CRUD and upsert semantics.
|
|
99
|
+
- Query operator semantics.
|
|
100
|
+
- Serialization of dates and null values.
|
|
101
|
+
- Conditional writes and revision conflicts.
|
|
102
|
+
- Unique constraint behavior.
|
|
103
|
+
- Cursor pagination.
|
|
104
|
+
- Atomic batch rollback.
|
|
105
|
+
- Capability reporting.
|
|
106
|
+
- Migration ordering and concurrency.
|
|
107
|
+
|
|
108
|
+
## Build
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
bun run build
|
|
112
|
+
bun run typecheck
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The typecheck includes all files under `tests/conformance` and loads Bun's
|
|
116
|
+
test types for their `bun:test` imports.
|
|
117
|
+
|
|
118
|
+
This package contains contracts only. It does not provide a production
|
|
119
|
+
database implementation or an in-memory persistence implementation.
|
package/dist/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sjoerd van Den Berg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/errors/storage.errors.ts
|
|
2
|
+
var StorageError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
cause;
|
|
5
|
+
details;
|
|
6
|
+
constructor(code, message, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "StorageError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.cause = options?.cause;
|
|
11
|
+
this.details = options?.details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var StorageCapabilityError = class extends StorageError {
|
|
15
|
+
capability;
|
|
16
|
+
constructor(capability, message = `Storage adapter does not support required capability: ${capability}`) {
|
|
17
|
+
super("CAPABILITY", message, { details: { capability } });
|
|
18
|
+
this.name = "StorageCapabilityError";
|
|
19
|
+
this.capability = capability;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var StorageConflictError = class extends StorageError {
|
|
23
|
+
constructor(message = "The storage operation conflicted with the current record state.") {
|
|
24
|
+
super("CONFLICT", message);
|
|
25
|
+
this.name = "StorageConflictError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var StorageDuplicateError = class extends StorageError {
|
|
29
|
+
constraint;
|
|
30
|
+
constructor(constraint) {
|
|
31
|
+
super("DUPLICATE", `A record violates the unique constraint "${constraint}".`, { details: { constraint } });
|
|
32
|
+
this.name = "StorageDuplicateError";
|
|
33
|
+
this.constraint = constraint;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var StorageValidationError = class extends StorageError {
|
|
37
|
+
constructor(message, details) {
|
|
38
|
+
super("VALIDATION", message, { details });
|
|
39
|
+
this.name = "StorageValidationError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var StorageAtomicityError = class extends StorageError {
|
|
43
|
+
constructor(message = "The adapter cannot guarantee atomic execution and rollback.") {
|
|
44
|
+
super("ATOMICITY", message);
|
|
45
|
+
this.name = "StorageAtomicityError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.StorageAtomicityError = StorageAtomicityError;
|
|
49
|
+
exports.StorageCapabilityError = StorageCapabilityError;
|
|
50
|
+
exports.StorageConflictError = StorageConflictError;
|
|
51
|
+
exports.StorageDuplicateError = StorageDuplicateError;
|
|
52
|
+
exports.StorageError = StorageError;
|
|
53
|
+
exports.StorageValidationError = StorageValidationError;
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=storage.errors.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.errors.cjs","names":[],"sources":["../../src/errors/storage.errors.ts"],"sourcesContent":["export type StorageErrorCode =\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"DUPLICATE\"\n | \"VALIDATION\"\n | \"CAPABILITY\"\n | \"ATOMICITY\"\n | \"MIGRATION\"\n | \"QUERY\"\n | \"UNKNOWN\";\n\nexport class StorageError extends Error {\n readonly code: StorageErrorCode;\n override readonly cause?: unknown;\n readonly details?: Readonly<Record<string, unknown>>;\n\n constructor(\n code: StorageErrorCode,\n message: string,\n options?: {\n readonly cause?: unknown;\n readonly details?: Readonly<Record<string, unknown>>;\n },\n ) {\n super(message);\n this.name = \"StorageError\";\n this.code = code;\n this.cause = options?.cause;\n this.details = options?.details;\n }\n}\n\nexport class StorageCapabilityError extends StorageError {\n readonly capability: string;\n\n constructor(\n capability: string,\n message = `Storage adapter does not support required capability: ${capability}`,\n ) {\n super(\"CAPABILITY\", message, {\n details: { capability },\n });\n\n this.name = \"StorageCapabilityError\";\n this.capability = capability;\n }\n}\n\nexport class StorageConflictError extends StorageError {\n constructor(\n message = \"The storage operation conflicted with the current record state.\",\n ) {\n super(\"CONFLICT\", message);\n this.name = \"StorageConflictError\";\n }\n}\n\nexport class StorageDuplicateError extends StorageError {\n readonly constraint: string;\n\n constructor(constraint: string) {\n super(\n \"DUPLICATE\",\n `A record violates the unique constraint \"${constraint}\".`,\n {\n details: { constraint },\n },\n );\n\n this.name = \"StorageDuplicateError\";\n this.constraint = constraint;\n }\n}\n\nexport class StorageValidationError extends StorageError {\n constructor(message: string, details?: Readonly<Record<string, unknown>>) {\n super(\"VALIDATION\", message, { details });\n this.name = \"StorageValidationError\";\n }\n}\n\nexport class StorageAtomicityError extends StorageError {\n constructor(\n message = \"The adapter cannot guarantee atomic execution and rollback.\",\n ) {\n super(\"ATOMICITY\", message);\n this.name = \"StorageAtomicityError\";\n }\n}\n"],"mappings":";AAWA,IAAa,eAAb,cAAkC,MAAM;CACtC;CACA;CACA;CAEA,YACE,MACA,SACA,SAIA;EACA,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,QAAQ,SAAS;EACtB,KAAK,UAAU,SAAS;CAC1B;AACF;AAEA,IAAa,yBAAb,cAA4C,aAAa;CACvD;CAEA,YACE,YACA,UAAU,yDAAyD,cACnE;EACA,MAAM,cAAc,SAAS,EAC3B,SAAS,EAAE,WAAW,EACxB,CAAC;EAED,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;AAEA,IAAa,uBAAb,cAA0C,aAAa;CACrD,YACE,UAAU,mEACV;EACA,MAAM,YAAY,OAAO;EACzB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,aAAa;CACtD;CAEA,YAAY,YAAoB;EAC9B,MACE,aACA,4CAA4C,WAAW,KACvD,EACE,SAAS,EAAE,WAAW,EACxB,CACF;EAEA,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;AAEA,IAAa,yBAAb,cAA4C,aAAa;CACvD,YAAY,SAAiB,SAA6C;EACxE,MAAM,cAAc,SAAS,EAAE,QAAQ,CAAC;EACxC,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,aAAa;CACtD,YACE,UAAU,+DACV;EACA,MAAM,aAAa,OAAO;EAC1B,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type StorageErrorCode = "NOT_FOUND" | "CONFLICT" | "DUPLICATE" | "VALIDATION" | "CAPABILITY" | "ATOMICITY" | "MIGRATION" | "QUERY" | "UNKNOWN";
|
|
2
|
+
export declare class StorageError extends Error {
|
|
3
|
+
readonly code: StorageErrorCode;
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
6
|
+
constructor(code: StorageErrorCode, message: string, options?: {
|
|
7
|
+
readonly cause?: unknown;
|
|
8
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare class StorageCapabilityError extends StorageError {
|
|
12
|
+
readonly capability: string;
|
|
13
|
+
constructor(capability: string, message?: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class StorageConflictError extends StorageError {
|
|
16
|
+
constructor(message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class StorageDuplicateError extends StorageError {
|
|
19
|
+
readonly constraint: string;
|
|
20
|
+
constructor(constraint: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class StorageValidationError extends StorageError {
|
|
23
|
+
constructor(message: string, details?: Readonly<Record<string, unknown>>);
|
|
24
|
+
}
|
|
25
|
+
export declare class StorageAtomicityError extends StorageError {
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type StorageErrorCode = "NOT_FOUND" | "CONFLICT" | "DUPLICATE" | "VALIDATION" | "CAPABILITY" | "ATOMICITY" | "MIGRATION" | "QUERY" | "UNKNOWN";
|
|
2
|
+
export declare class StorageError extends Error {
|
|
3
|
+
readonly code: StorageErrorCode;
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
6
|
+
constructor(code: StorageErrorCode, message: string, options?: {
|
|
7
|
+
readonly cause?: unknown;
|
|
8
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare class StorageCapabilityError extends StorageError {
|
|
12
|
+
readonly capability: string;
|
|
13
|
+
constructor(capability: string, message?: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class StorageConflictError extends StorageError {
|
|
16
|
+
constructor(message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class StorageDuplicateError extends StorageError {
|
|
19
|
+
readonly constraint: string;
|
|
20
|
+
constructor(constraint: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class StorageValidationError extends StorageError {
|
|
23
|
+
constructor(message: string, details?: Readonly<Record<string, unknown>>);
|
|
24
|
+
}
|
|
25
|
+
export declare class StorageAtomicityError extends StorageError {
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/errors/storage.errors.ts
|
|
2
|
+
var StorageError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
cause;
|
|
5
|
+
details;
|
|
6
|
+
constructor(code, message, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "StorageError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.cause = options?.cause;
|
|
11
|
+
this.details = options?.details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var StorageCapabilityError = class extends StorageError {
|
|
15
|
+
capability;
|
|
16
|
+
constructor(capability, message = `Storage adapter does not support required capability: ${capability}`) {
|
|
17
|
+
super("CAPABILITY", message, { details: { capability } });
|
|
18
|
+
this.name = "StorageCapabilityError";
|
|
19
|
+
this.capability = capability;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var StorageConflictError = class extends StorageError {
|
|
23
|
+
constructor(message = "The storage operation conflicted with the current record state.") {
|
|
24
|
+
super("CONFLICT", message);
|
|
25
|
+
this.name = "StorageConflictError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var StorageDuplicateError = class extends StorageError {
|
|
29
|
+
constraint;
|
|
30
|
+
constructor(constraint) {
|
|
31
|
+
super("DUPLICATE", `A record violates the unique constraint "${constraint}".`, { details: { constraint } });
|
|
32
|
+
this.name = "StorageDuplicateError";
|
|
33
|
+
this.constraint = constraint;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var StorageValidationError = class extends StorageError {
|
|
37
|
+
constructor(message, details) {
|
|
38
|
+
super("VALIDATION", message, { details });
|
|
39
|
+
this.name = "StorageValidationError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var StorageAtomicityError = class extends StorageError {
|
|
43
|
+
constructor(message = "The adapter cannot guarantee atomic execution and rollback.") {
|
|
44
|
+
super("ATOMICITY", message);
|
|
45
|
+
this.name = "StorageAtomicityError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export { StorageAtomicityError, StorageCapabilityError, StorageConflictError, StorageDuplicateError, StorageError, StorageValidationError };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=storage.errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.errors.js","names":[],"sources":["../../src/errors/storage.errors.ts"],"sourcesContent":["export type StorageErrorCode =\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"DUPLICATE\"\n | \"VALIDATION\"\n | \"CAPABILITY\"\n | \"ATOMICITY\"\n | \"MIGRATION\"\n | \"QUERY\"\n | \"UNKNOWN\";\n\nexport class StorageError extends Error {\n readonly code: StorageErrorCode;\n override readonly cause?: unknown;\n readonly details?: Readonly<Record<string, unknown>>;\n\n constructor(\n code: StorageErrorCode,\n message: string,\n options?: {\n readonly cause?: unknown;\n readonly details?: Readonly<Record<string, unknown>>;\n },\n ) {\n super(message);\n this.name = \"StorageError\";\n this.code = code;\n this.cause = options?.cause;\n this.details = options?.details;\n }\n}\n\nexport class StorageCapabilityError extends StorageError {\n readonly capability: string;\n\n constructor(\n capability: string,\n message = `Storage adapter does not support required capability: ${capability}`,\n ) {\n super(\"CAPABILITY\", message, {\n details: { capability },\n });\n\n this.name = \"StorageCapabilityError\";\n this.capability = capability;\n }\n}\n\nexport class StorageConflictError extends StorageError {\n constructor(\n message = \"The storage operation conflicted with the current record state.\",\n ) {\n super(\"CONFLICT\", message);\n this.name = \"StorageConflictError\";\n }\n}\n\nexport class StorageDuplicateError extends StorageError {\n readonly constraint: string;\n\n constructor(constraint: string) {\n super(\n \"DUPLICATE\",\n `A record violates the unique constraint \"${constraint}\".`,\n {\n details: { constraint },\n },\n );\n\n this.name = \"StorageDuplicateError\";\n this.constraint = constraint;\n }\n}\n\nexport class StorageValidationError extends StorageError {\n constructor(message: string, details?: Readonly<Record<string, unknown>>) {\n super(\"VALIDATION\", message, { details });\n this.name = \"StorageValidationError\";\n }\n}\n\nexport class StorageAtomicityError extends StorageError {\n constructor(\n message = \"The adapter cannot guarantee atomic execution and rollback.\",\n ) {\n super(\"ATOMICITY\", message);\n this.name = \"StorageAtomicityError\";\n }\n}\n"],"mappings":";AAWA,IAAa,eAAb,cAAkC,MAAM;CACtC;CACA;CACA;CAEA,YACE,MACA,SACA,SAIA;EACA,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,QAAQ,SAAS;EACtB,KAAK,UAAU,SAAS;CAC1B;AACF;AAEA,IAAa,yBAAb,cAA4C,aAAa;CACvD;CAEA,YACE,YACA,UAAU,yDAAyD,cACnE;EACA,MAAM,cAAc,SAAS,EAC3B,SAAS,EAAE,WAAW,EACxB,CAAC;EAED,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;AAEA,IAAa,uBAAb,cAA0C,aAAa;CACrD,YACE,UAAU,mEACV;EACA,MAAM,YAAY,OAAO;EACzB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,aAAa;CACtD;CAEA,YAAY,YAAoB;EAC9B,MACE,aACA,4CAA4C,WAAW,KACvD,EACE,SAAS,EAAE,WAAW,EACxB,CACF;EAEA,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;AAEA,IAAa,yBAAb,cAA4C,aAAa;CACvD,YAAY,SAAiB,SAA6C;EACxE,MAAM,cAAc,SAAS,EAAE,QAAQ,CAAC;EACxC,KAAK,OAAO;CACd;AACF;AAEA,IAAa,wBAAb,cAA2C,aAAa;CACtD,YACE,UAAU,+DACV;EACA,MAAM,aAAa,OAAO;EAC1B,KAAK,OAAO;CACd;AACF"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_capabilities_types = require("./types/capabilities.types.cjs");
|
|
3
|
+
const require_storage_errors = require("./errors/storage.errors.cjs");
|
|
4
|
+
const require_query_utils = require("./utils/query.utils.cjs");
|
|
5
|
+
exports.DEFAULT_STORAGE_CAPABILITIES = require_capabilities_types.DEFAULT_STORAGE_CAPABILITIES;
|
|
6
|
+
exports.StorageAtomicityError = require_storage_errors.StorageAtomicityError;
|
|
7
|
+
exports.StorageCapabilityError = require_storage_errors.StorageCapabilityError;
|
|
8
|
+
exports.StorageConflictError = require_storage_errors.StorageConflictError;
|
|
9
|
+
exports.StorageDuplicateError = require_storage_errors.StorageDuplicateError;
|
|
10
|
+
exports.StorageError = require_storage_errors.StorageError;
|
|
11
|
+
exports.StorageValidationError = require_storage_errors.StorageValidationError;
|
|
12
|
+
exports.validateQuery = require_query_utils.validateQuery;
|
|
13
|
+
exports.validateWhere = require_query_utils.validateWhere;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StorageCollection, StorageCollectionName, StorageCollectionOptions, StorageField, StorageIndex, StorageRecord, StorageScalar, StorageSchema } from "./types/collection.types.cjs";
|
|
2
|
+
import { StorageComparison, StorageCursor, StorageCursorQuery, StorageOrder, StoragePage, StorageQuery, StorageWhere } from "./types/query.types.cjs";
|
|
3
|
+
import { DEFAULT_STORAGE_CAPABILITIES, StorageCapabilities, StorageCapability, StorageCapabilityReport } from "./types/capabilities.types.cjs";
|
|
4
|
+
import { StorageAdapter, StorageAdapterOptions, StorageBatchOperation, StorageBatchResult, StorageCondition, StorageInsert, StorageUpdate } from "./types/adapter.types.cjs";
|
|
5
|
+
import { StorageMigration, StorageMigrationAdapter, StorageMigrationContext, StorageMigrationOperation, StorageMigrationOptions, StorageMigrationRecord, StorageMigrationResult, StorageMigrationStatus } from "./types/migration.types.cjs";
|
|
6
|
+
import { StorageIndexDefinition, StorageSchemaAdapter, StorageSchemaRegistry } from "./types/schema.types.cjs";
|
|
7
|
+
import { StorageAtomicityError, StorageCapabilityError, StorageConflictError, StorageDuplicateError, StorageError, StorageValidationError } from "./errors/storage.errors.cjs";
|
|
8
|
+
import { validateQuery, validateWhere } from "./utils/query.utils.cjs";
|
|
9
|
+
export { DEFAULT_STORAGE_CAPABILITIES, type StorageAdapter, type StorageAdapterOptions, StorageAtomicityError, type StorageBatchOperation, type StorageBatchResult, type StorageCapabilities, type StorageCapability, StorageCapabilityError, type StorageCapabilityReport, type StorageCollection, type StorageCollectionName, type StorageCollectionOptions, type StorageComparison, type StorageCondition, StorageConflictError, type StorageCursor, type StorageCursorQuery, StorageDuplicateError, StorageError, type StorageField, type StorageIndex, type StorageIndexDefinition, type StorageInsert, type StorageMigration, type StorageMigrationAdapter, type StorageMigrationContext, type StorageMigrationOperation, type StorageMigrationOptions, type StorageMigrationRecord, type StorageMigrationResult, type StorageMigrationStatus, type StorageOrder, type StoragePage, type StorageQuery, type StorageRecord, type StorageScalar, type StorageSchema, type StorageSchemaAdapter, type StorageSchemaRegistry, type StorageUpdate, StorageValidationError, type StorageWhere, validateQuery, validateWhere };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StorageCollection, StorageCollectionName, StorageCollectionOptions, StorageField, StorageIndex, StorageRecord, StorageScalar, StorageSchema } from "./types/collection.types.js";
|
|
2
|
+
import { StorageComparison, StorageCursor, StorageCursorQuery, StorageOrder, StoragePage, StorageQuery, StorageWhere } from "./types/query.types.js";
|
|
3
|
+
import { DEFAULT_STORAGE_CAPABILITIES, StorageCapabilities, StorageCapability, StorageCapabilityReport } from "./types/capabilities.types.js";
|
|
4
|
+
import { StorageAdapter, StorageAdapterOptions, StorageBatchOperation, StorageBatchResult, StorageCondition, StorageInsert, StorageUpdate } from "./types/adapter.types.js";
|
|
5
|
+
import { StorageMigration, StorageMigrationAdapter, StorageMigrationContext, StorageMigrationOperation, StorageMigrationOptions, StorageMigrationRecord, StorageMigrationResult, StorageMigrationStatus } from "./types/migration.types.js";
|
|
6
|
+
import { StorageIndexDefinition, StorageSchemaAdapter, StorageSchemaRegistry } from "./types/schema.types.js";
|
|
7
|
+
import { StorageAtomicityError, StorageCapabilityError, StorageConflictError, StorageDuplicateError, StorageError, StorageValidationError } from "./errors/storage.errors.js";
|
|
8
|
+
import { validateQuery, validateWhere } from "./utils/query.utils.js";
|
|
9
|
+
export { DEFAULT_STORAGE_CAPABILITIES, type StorageAdapter, type StorageAdapterOptions, StorageAtomicityError, type StorageBatchOperation, type StorageBatchResult, type StorageCapabilities, type StorageCapability, StorageCapabilityError, type StorageCapabilityReport, type StorageCollection, type StorageCollectionName, type StorageCollectionOptions, type StorageComparison, type StorageCondition, StorageConflictError, type StorageCursor, type StorageCursorQuery, StorageDuplicateError, StorageError, type StorageField, type StorageIndex, type StorageIndexDefinition, type StorageInsert, type StorageMigration, type StorageMigrationAdapter, type StorageMigrationContext, type StorageMigrationOperation, type StorageMigrationOptions, type StorageMigrationRecord, type StorageMigrationResult, type StorageMigrationStatus, type StorageOrder, type StoragePage, type StorageQuery, type StorageRecord, type StorageScalar, type StorageSchema, type StorageSchemaAdapter, type StorageSchemaRegistry, type StorageUpdate, StorageValidationError, type StorageWhere, validateQuery, validateWhere };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_STORAGE_CAPABILITIES } from "./types/capabilities.types.js";
|
|
2
|
+
import { StorageAtomicityError, StorageCapabilityError, StorageConflictError, StorageDuplicateError, StorageError, StorageValidationError } from "./errors/storage.errors.js";
|
|
3
|
+
import { validateQuery, validateWhere } from "./utils/query.utils.js";
|
|
4
|
+
export { DEFAULT_STORAGE_CAPABILITIES, StorageAtomicityError, StorageCapabilityError, StorageConflictError, StorageDuplicateError, StorageError, StorageValidationError, validateQuery, validateWhere };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StorageCollection, StorageCollectionName, StorageRecord } from "./collection.types.cjs";
|
|
2
|
+
import { StorageCapabilities, StorageCapabilityReport } from "./capabilities.types.cjs";
|
|
3
|
+
import { StorageCursorQuery, StoragePage, StorageQuery } from "./query.types.cjs";
|
|
4
|
+
export type StorageInsert<T extends StorageRecord> = Omit<T, "createdAt" | "updatedAt" | "revision"> & {
|
|
5
|
+
readonly createdAt?: Date;
|
|
6
|
+
readonly updatedAt?: Date;
|
|
7
|
+
readonly revision?: number;
|
|
8
|
+
};
|
|
9
|
+
export type StorageUpdate<T extends StorageRecord> = Partial<Omit<T, "id" | "createdAt" | "revision">>;
|
|
10
|
+
export type StorageCondition = {
|
|
11
|
+
readonly expectedRevision?: number;
|
|
12
|
+
readonly expectedUpdatedAt?: Date;
|
|
13
|
+
};
|
|
14
|
+
type StorageBatchOperationItem<T extends StorageRecord> = {
|
|
15
|
+
readonly type: "insert";
|
|
16
|
+
readonly collection: StorageCollectionName;
|
|
17
|
+
readonly record: StorageInsert<T>;
|
|
18
|
+
} | {
|
|
19
|
+
readonly type: "update";
|
|
20
|
+
readonly collection: StorageCollectionName;
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly condition?: StorageCondition;
|
|
23
|
+
readonly changes: StorageUpdate<T>;
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "delete";
|
|
26
|
+
readonly collection: StorageCollectionName;
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly condition?: StorageCondition;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Distributes over unions so an atomic batch can contain records
|
|
32
|
+
* from different collections with different record types.
|
|
33
|
+
*/
|
|
34
|
+
export type StorageBatchOperation<T extends StorageRecord> = T extends StorageRecord ? StorageBatchOperationItem<T> : never;
|
|
35
|
+
export type StorageBatchResult<T extends StorageRecord> = {
|
|
36
|
+
readonly results: readonly ({
|
|
37
|
+
readonly type: "insert";
|
|
38
|
+
readonly record: T;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "update";
|
|
41
|
+
readonly record: T | null;
|
|
42
|
+
} | {
|
|
43
|
+
readonly type: "delete";
|
|
44
|
+
readonly deleted: boolean;
|
|
45
|
+
})[];
|
|
46
|
+
};
|
|
47
|
+
export type StorageAdapterOptions = {
|
|
48
|
+
readonly requireAtomicBatch?: boolean;
|
|
49
|
+
};
|
|
50
|
+
export interface StorageAdapter {
|
|
51
|
+
readonly capabilities: StorageCapabilities;
|
|
52
|
+
getCapabilities(): StorageCapabilityReport;
|
|
53
|
+
registerCollection<T extends StorageRecord>(collection: StorageCollection<T>): Promise<void>;
|
|
54
|
+
findById<T extends StorageRecord>(collection: StorageCollectionName, id: string): Promise<T | null>;
|
|
55
|
+
findOne<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<T | null>;
|
|
56
|
+
findMany<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<readonly T[]>;
|
|
57
|
+
findPage<T extends StorageRecord>(collection: StorageCollectionName, query: StorageCursorQuery<T>): Promise<StoragePage<T>>;
|
|
58
|
+
insert<T extends StorageRecord>(collection: StorageCollectionName, record: StorageInsert<T>): Promise<T>;
|
|
59
|
+
upsert<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, record: StorageInsert<T>): Promise<T>;
|
|
60
|
+
update<T extends StorageRecord>(collection: StorageCollectionName, id: string, changes: StorageUpdate<T>, condition?: StorageCondition): Promise<T | null>;
|
|
61
|
+
updateMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, changes: StorageUpdate<T>): Promise<number>;
|
|
62
|
+
delete<T extends StorageRecord>(collection: StorageCollectionName, id: string, condition?: StorageCondition): Promise<boolean>;
|
|
63
|
+
deleteMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<number>;
|
|
64
|
+
exists<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<boolean>;
|
|
65
|
+
count<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<number>;
|
|
66
|
+
atomicBatch<T extends StorageRecord>(operations: readonly StorageBatchOperation<T>[]): Promise<StorageBatchResult<T>>;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StorageCollection, StorageCollectionName, StorageRecord } from "./collection.types.js";
|
|
2
|
+
import { StorageCapabilities, StorageCapabilityReport } from "./capabilities.types.js";
|
|
3
|
+
import { StorageCursorQuery, StoragePage, StorageQuery } from "./query.types.js";
|
|
4
|
+
export type StorageInsert<T extends StorageRecord> = Omit<T, "createdAt" | "updatedAt" | "revision"> & {
|
|
5
|
+
readonly createdAt?: Date;
|
|
6
|
+
readonly updatedAt?: Date;
|
|
7
|
+
readonly revision?: number;
|
|
8
|
+
};
|
|
9
|
+
export type StorageUpdate<T extends StorageRecord> = Partial<Omit<T, "id" | "createdAt" | "revision">>;
|
|
10
|
+
export type StorageCondition = {
|
|
11
|
+
readonly expectedRevision?: number;
|
|
12
|
+
readonly expectedUpdatedAt?: Date;
|
|
13
|
+
};
|
|
14
|
+
type StorageBatchOperationItem<T extends StorageRecord> = {
|
|
15
|
+
readonly type: "insert";
|
|
16
|
+
readonly collection: StorageCollectionName;
|
|
17
|
+
readonly record: StorageInsert<T>;
|
|
18
|
+
} | {
|
|
19
|
+
readonly type: "update";
|
|
20
|
+
readonly collection: StorageCollectionName;
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly condition?: StorageCondition;
|
|
23
|
+
readonly changes: StorageUpdate<T>;
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "delete";
|
|
26
|
+
readonly collection: StorageCollectionName;
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly condition?: StorageCondition;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Distributes over unions so an atomic batch can contain records
|
|
32
|
+
* from different collections with different record types.
|
|
33
|
+
*/
|
|
34
|
+
export type StorageBatchOperation<T extends StorageRecord> = T extends StorageRecord ? StorageBatchOperationItem<T> : never;
|
|
35
|
+
export type StorageBatchResult<T extends StorageRecord> = {
|
|
36
|
+
readonly results: readonly ({
|
|
37
|
+
readonly type: "insert";
|
|
38
|
+
readonly record: T;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "update";
|
|
41
|
+
readonly record: T | null;
|
|
42
|
+
} | {
|
|
43
|
+
readonly type: "delete";
|
|
44
|
+
readonly deleted: boolean;
|
|
45
|
+
})[];
|
|
46
|
+
};
|
|
47
|
+
export type StorageAdapterOptions = {
|
|
48
|
+
readonly requireAtomicBatch?: boolean;
|
|
49
|
+
};
|
|
50
|
+
export interface StorageAdapter {
|
|
51
|
+
readonly capabilities: StorageCapabilities;
|
|
52
|
+
getCapabilities(): StorageCapabilityReport;
|
|
53
|
+
registerCollection<T extends StorageRecord>(collection: StorageCollection<T>): Promise<void>;
|
|
54
|
+
findById<T extends StorageRecord>(collection: StorageCollectionName, id: string): Promise<T | null>;
|
|
55
|
+
findOne<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<T | null>;
|
|
56
|
+
findMany<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<readonly T[]>;
|
|
57
|
+
findPage<T extends StorageRecord>(collection: StorageCollectionName, query: StorageCursorQuery<T>): Promise<StoragePage<T>>;
|
|
58
|
+
insert<T extends StorageRecord>(collection: StorageCollectionName, record: StorageInsert<T>): Promise<T>;
|
|
59
|
+
upsert<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, record: StorageInsert<T>): Promise<T>;
|
|
60
|
+
update<T extends StorageRecord>(collection: StorageCollectionName, id: string, changes: StorageUpdate<T>, condition?: StorageCondition): Promise<T | null>;
|
|
61
|
+
updateMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, changes: StorageUpdate<T>): Promise<number>;
|
|
62
|
+
delete<T extends StorageRecord>(collection: StorageCollectionName, id: string, condition?: StorageCondition): Promise<boolean>;
|
|
63
|
+
deleteMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<number>;
|
|
64
|
+
exists<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<boolean>;
|
|
65
|
+
count<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<number>;
|
|
66
|
+
atomicBatch<T extends StorageRecord>(operations: readonly StorageBatchOperation<T>[]): Promise<StorageBatchResult<T>>;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/types/capabilities.types.ts
|
|
2
|
+
const DEFAULT_STORAGE_CAPABILITIES = {
|
|
3
|
+
transactions: false,
|
|
4
|
+
atomicBatch: false,
|
|
5
|
+
conditionalWrites: false,
|
|
6
|
+
uniqueConstraints: false,
|
|
7
|
+
upsert: false,
|
|
8
|
+
cursorPagination: false,
|
|
9
|
+
migrations: false,
|
|
10
|
+
ttlIndexes: false
|
|
11
|
+
};
|
|
12
|
+
exports.DEFAULT_STORAGE_CAPABILITIES = DEFAULT_STORAGE_CAPABILITIES;
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=capabilities.types.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.types.cjs","names":[],"sources":["../../src/types/capabilities.types.ts"],"sourcesContent":["export type StorageCapabilities = {\n readonly transactions: boolean;\n readonly atomicBatch: boolean;\n readonly conditionalWrites: boolean;\n readonly uniqueConstraints: boolean;\n readonly upsert: boolean;\n readonly cursorPagination: boolean;\n readonly migrations: boolean;\n readonly ttlIndexes: boolean;\n};\n\nexport type StorageCapability = keyof StorageCapabilities;\n\nexport type StorageCapabilityReport = {\n readonly capabilities: StorageCapabilities;\n readonly limitations?: readonly string[];\n};\n\nexport const DEFAULT_STORAGE_CAPABILITIES: StorageCapabilities = {\n transactions: false,\n atomicBatch: false,\n conditionalWrites: false,\n uniqueConstraints: false,\n upsert: false,\n cursorPagination: false,\n migrations: false,\n ttlIndexes: false,\n};\n"],"mappings":";AAkBA,MAAa,+BAAoD;CAC/D,cAAc;CACd,aAAa;CACb,mBAAmB;CACnB,mBAAmB;CACnB,QAAQ;CACR,kBAAkB;CAClB,YAAY;CACZ,YAAY;AACd"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StorageCapabilities = {
|
|
2
|
+
readonly transactions: boolean;
|
|
3
|
+
readonly atomicBatch: boolean;
|
|
4
|
+
readonly conditionalWrites: boolean;
|
|
5
|
+
readonly uniqueConstraints: boolean;
|
|
6
|
+
readonly upsert: boolean;
|
|
7
|
+
readonly cursorPagination: boolean;
|
|
8
|
+
readonly migrations: boolean;
|
|
9
|
+
readonly ttlIndexes: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type StorageCapability = keyof StorageCapabilities;
|
|
12
|
+
export type StorageCapabilityReport = {
|
|
13
|
+
readonly capabilities: StorageCapabilities;
|
|
14
|
+
readonly limitations?: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
export declare const DEFAULT_STORAGE_CAPABILITIES: StorageCapabilities;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StorageCapabilities = {
|
|
2
|
+
readonly transactions: boolean;
|
|
3
|
+
readonly atomicBatch: boolean;
|
|
4
|
+
readonly conditionalWrites: boolean;
|
|
5
|
+
readonly uniqueConstraints: boolean;
|
|
6
|
+
readonly upsert: boolean;
|
|
7
|
+
readonly cursorPagination: boolean;
|
|
8
|
+
readonly migrations: boolean;
|
|
9
|
+
readonly ttlIndexes: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type StorageCapability = keyof StorageCapabilities;
|
|
12
|
+
export type StorageCapabilityReport = {
|
|
13
|
+
readonly capabilities: StorageCapabilities;
|
|
14
|
+
readonly limitations?: readonly string[];
|
|
15
|
+
};
|
|
16
|
+
export declare const DEFAULT_STORAGE_CAPABILITIES: StorageCapabilities;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/types/capabilities.types.ts
|
|
2
|
+
const DEFAULT_STORAGE_CAPABILITIES = {
|
|
3
|
+
transactions: false,
|
|
4
|
+
atomicBatch: false,
|
|
5
|
+
conditionalWrites: false,
|
|
6
|
+
uniqueConstraints: false,
|
|
7
|
+
upsert: false,
|
|
8
|
+
cursorPagination: false,
|
|
9
|
+
migrations: false,
|
|
10
|
+
ttlIndexes: false
|
|
11
|
+
};
|
|
12
|
+
export { DEFAULT_STORAGE_CAPABILITIES };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=capabilities.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.types.js","names":[],"sources":["../../src/types/capabilities.types.ts"],"sourcesContent":["export type StorageCapabilities = {\n readonly transactions: boolean;\n readonly atomicBatch: boolean;\n readonly conditionalWrites: boolean;\n readonly uniqueConstraints: boolean;\n readonly upsert: boolean;\n readonly cursorPagination: boolean;\n readonly migrations: boolean;\n readonly ttlIndexes: boolean;\n};\n\nexport type StorageCapability = keyof StorageCapabilities;\n\nexport type StorageCapabilityReport = {\n readonly capabilities: StorageCapabilities;\n readonly limitations?: readonly string[];\n};\n\nexport const DEFAULT_STORAGE_CAPABILITIES: StorageCapabilities = {\n transactions: false,\n atomicBatch: false,\n conditionalWrites: false,\n uniqueConstraints: false,\n upsert: false,\n cursorPagination: false,\n migrations: false,\n ttlIndexes: false,\n};\n"],"mappings":";AAkBA,MAAa,+BAAoD;CAC/D,cAAc;CACd,aAAa;CACb,mBAAmB;CACnB,mBAAmB;CACnB,QAAQ;CACR,kBAAkB;CAClB,YAAY;CACZ,YAAY;AACd"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type StorageScalar = string | number | boolean | Date | null;
|
|
2
|
+
export type StorageRecord = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly createdAt: Date;
|
|
5
|
+
readonly updatedAt: Date;
|
|
6
|
+
readonly revokedAt?: Date | null;
|
|
7
|
+
readonly revision: number;
|
|
8
|
+
};
|
|
9
|
+
export type StorageCollectionName = string;
|
|
10
|
+
export type StorageField<T> = { [K in keyof T]-?: T[K] extends StorageScalar ? K : never; }[keyof T] & string;
|
|
11
|
+
export type StorageCollectionOptions<T extends StorageRecord> = {
|
|
12
|
+
readonly name: StorageCollectionName;
|
|
13
|
+
readonly schema?: StorageSchema<T>;
|
|
14
|
+
readonly indexes?: readonly StorageIndex<T>[];
|
|
15
|
+
};
|
|
16
|
+
export type StorageCollection<T extends StorageRecord> = StorageCollectionOptions<T>;
|
|
17
|
+
export type StorageSchema<T extends StorageRecord> = {
|
|
18
|
+
readonly validate: (record: unknown) => record is T;
|
|
19
|
+
};
|
|
20
|
+
export type StorageIndex<T extends StorageRecord> = {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly fields: readonly StorageField<T>[];
|
|
23
|
+
readonly unique?: boolean;
|
|
24
|
+
readonly expireAfterSeconds?: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type StorageScalar = string | number | boolean | Date | null;
|
|
2
|
+
export type StorageRecord = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly createdAt: Date;
|
|
5
|
+
readonly updatedAt: Date;
|
|
6
|
+
readonly revokedAt?: Date | null;
|
|
7
|
+
readonly revision: number;
|
|
8
|
+
};
|
|
9
|
+
export type StorageCollectionName = string;
|
|
10
|
+
export type StorageField<T> = { [K in keyof T]-?: T[K] extends StorageScalar ? K : never; }[keyof T] & string;
|
|
11
|
+
export type StorageCollectionOptions<T extends StorageRecord> = {
|
|
12
|
+
readonly name: StorageCollectionName;
|
|
13
|
+
readonly schema?: StorageSchema<T>;
|
|
14
|
+
readonly indexes?: readonly StorageIndex<T>[];
|
|
15
|
+
};
|
|
16
|
+
export type StorageCollection<T extends StorageRecord> = StorageCollectionOptions<T>;
|
|
17
|
+
export type StorageSchema<T extends StorageRecord> = {
|
|
18
|
+
readonly validate: (record: unknown) => record is T;
|
|
19
|
+
};
|
|
20
|
+
export type StorageIndex<T extends StorageRecord> = {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly fields: readonly StorageField<T>[];
|
|
23
|
+
readonly unique?: boolean;
|
|
24
|
+
readonly expireAfterSeconds?: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type StorageMigrationContext = {
|
|
2
|
+
readonly execute: (operation: StorageMigrationOperation) => Promise<void>;
|
|
3
|
+
};
|
|
4
|
+
export type StorageMigrationOperation = {
|
|
5
|
+
readonly type: "createCollection";
|
|
6
|
+
readonly collection: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: "dropCollection";
|
|
9
|
+
readonly collection: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly type: "createIndex";
|
|
12
|
+
readonly collection: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly fields: readonly string[];
|
|
15
|
+
readonly unique?: boolean;
|
|
16
|
+
readonly expireAfterSeconds?: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "dropIndex";
|
|
19
|
+
readonly collection: string;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
};
|
|
22
|
+
export type StorageMigration = {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly version: number;
|
|
25
|
+
readonly description: string;
|
|
26
|
+
readonly up: (context: StorageMigrationContext) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export type StorageMigrationStatus = "pending" | "running" | "completed" | "failed";
|
|
29
|
+
export type StorageMigrationRecord = {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly version: number;
|
|
32
|
+
readonly description: string;
|
|
33
|
+
readonly status: StorageMigrationStatus;
|
|
34
|
+
readonly startedAt?: Date;
|
|
35
|
+
readonly completedAt?: Date;
|
|
36
|
+
readonly error?: string;
|
|
37
|
+
};
|
|
38
|
+
export type StorageMigrationResult = {
|
|
39
|
+
readonly applied: readonly number[];
|
|
40
|
+
readonly skipped: readonly number[];
|
|
41
|
+
readonly currentVersion: number;
|
|
42
|
+
};
|
|
43
|
+
export type StorageMigrationOptions = {
|
|
44
|
+
readonly targetVersion?: number;
|
|
45
|
+
readonly dryRun?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export interface StorageMigrationAdapter {
|
|
48
|
+
getMigrationStatus(): Promise<{
|
|
49
|
+
readonly currentVersion: number;
|
|
50
|
+
readonly migrations: readonly StorageMigrationRecord[];
|
|
51
|
+
}>;
|
|
52
|
+
runMigrations(migrations: readonly StorageMigration[], options?: StorageMigrationOptions): Promise<StorageMigrationResult>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type StorageMigrationContext = {
|
|
2
|
+
readonly execute: (operation: StorageMigrationOperation) => Promise<void>;
|
|
3
|
+
};
|
|
4
|
+
export type StorageMigrationOperation = {
|
|
5
|
+
readonly type: "createCollection";
|
|
6
|
+
readonly collection: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: "dropCollection";
|
|
9
|
+
readonly collection: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly type: "createIndex";
|
|
12
|
+
readonly collection: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly fields: readonly string[];
|
|
15
|
+
readonly unique?: boolean;
|
|
16
|
+
readonly expireAfterSeconds?: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "dropIndex";
|
|
19
|
+
readonly collection: string;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
};
|
|
22
|
+
export type StorageMigration = {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly version: number;
|
|
25
|
+
readonly description: string;
|
|
26
|
+
readonly up: (context: StorageMigrationContext) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export type StorageMigrationStatus = "pending" | "running" | "completed" | "failed";
|
|
29
|
+
export type StorageMigrationRecord = {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly version: number;
|
|
32
|
+
readonly description: string;
|
|
33
|
+
readonly status: StorageMigrationStatus;
|
|
34
|
+
readonly startedAt?: Date;
|
|
35
|
+
readonly completedAt?: Date;
|
|
36
|
+
readonly error?: string;
|
|
37
|
+
};
|
|
38
|
+
export type StorageMigrationResult = {
|
|
39
|
+
readonly applied: readonly number[];
|
|
40
|
+
readonly skipped: readonly number[];
|
|
41
|
+
readonly currentVersion: number;
|
|
42
|
+
};
|
|
43
|
+
export type StorageMigrationOptions = {
|
|
44
|
+
readonly targetVersion?: number;
|
|
45
|
+
readonly dryRun?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export interface StorageMigrationAdapter {
|
|
48
|
+
getMigrationStatus(): Promise<{
|
|
49
|
+
readonly currentVersion: number;
|
|
50
|
+
readonly migrations: readonly StorageMigrationRecord[];
|
|
51
|
+
}>;
|
|
52
|
+
runMigrations(migrations: readonly StorageMigration[], options?: StorageMigrationOptions): Promise<StorageMigrationResult>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StorageRecord, StorageScalar } from "./collection.types.cjs";
|
|
2
|
+
export type StorageComparison<T = StorageScalar> = {
|
|
3
|
+
readonly operator: "eq";
|
|
4
|
+
readonly value: T;
|
|
5
|
+
} | {
|
|
6
|
+
readonly operator: "ne";
|
|
7
|
+
readonly value: T;
|
|
8
|
+
} | {
|
|
9
|
+
readonly operator: "in";
|
|
10
|
+
readonly value: readonly T[];
|
|
11
|
+
} | {
|
|
12
|
+
readonly operator: "notIn";
|
|
13
|
+
readonly value: readonly T[];
|
|
14
|
+
} | {
|
|
15
|
+
readonly operator: "gt";
|
|
16
|
+
readonly value: T;
|
|
17
|
+
} | {
|
|
18
|
+
readonly operator: "gte";
|
|
19
|
+
readonly value: T;
|
|
20
|
+
} | {
|
|
21
|
+
readonly operator: "lt";
|
|
22
|
+
readonly value: T;
|
|
23
|
+
} | {
|
|
24
|
+
readonly operator: "lte";
|
|
25
|
+
readonly value: T;
|
|
26
|
+
} | {
|
|
27
|
+
readonly operator: "exists";
|
|
28
|
+
readonly value: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type StorageWhere<T extends object> = { readonly [K in keyof T]?: T[K] extends StorageScalar ? StorageComparison<T[K]> : StorageComparison<StorageScalar>; } & {
|
|
31
|
+
readonly and?: readonly StorageWhere<T>[];
|
|
32
|
+
readonly or?: readonly StorageWhere<T>[];
|
|
33
|
+
};
|
|
34
|
+
export type StorageOrder<T extends object> = {
|
|
35
|
+
readonly field: keyof T & string;
|
|
36
|
+
readonly direction: "asc" | "desc";
|
|
37
|
+
};
|
|
38
|
+
export type StorageQuery<T extends StorageRecord> = {
|
|
39
|
+
readonly where?: StorageWhere<T>;
|
|
40
|
+
readonly orderBy?: readonly StorageOrder<T>[];
|
|
41
|
+
readonly limit?: number;
|
|
42
|
+
readonly offset?: number;
|
|
43
|
+
};
|
|
44
|
+
export type StorageCursor = {
|
|
45
|
+
readonly value: string;
|
|
46
|
+
};
|
|
47
|
+
export type StorageCursorQuery<T extends StorageRecord> = Omit<StorageQuery<T>, "offset"> & {
|
|
48
|
+
readonly first: number;
|
|
49
|
+
readonly after?: StorageCursor;
|
|
50
|
+
};
|
|
51
|
+
export type StoragePage<T extends StorageRecord> = {
|
|
52
|
+
readonly items: readonly T[];
|
|
53
|
+
readonly nextCursor: StorageCursor | null;
|
|
54
|
+
readonly hasNextPage: boolean;
|
|
55
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StorageRecord, StorageScalar } from "./collection.types.js";
|
|
2
|
+
export type StorageComparison<T = StorageScalar> = {
|
|
3
|
+
readonly operator: "eq";
|
|
4
|
+
readonly value: T;
|
|
5
|
+
} | {
|
|
6
|
+
readonly operator: "ne";
|
|
7
|
+
readonly value: T;
|
|
8
|
+
} | {
|
|
9
|
+
readonly operator: "in";
|
|
10
|
+
readonly value: readonly T[];
|
|
11
|
+
} | {
|
|
12
|
+
readonly operator: "notIn";
|
|
13
|
+
readonly value: readonly T[];
|
|
14
|
+
} | {
|
|
15
|
+
readonly operator: "gt";
|
|
16
|
+
readonly value: T;
|
|
17
|
+
} | {
|
|
18
|
+
readonly operator: "gte";
|
|
19
|
+
readonly value: T;
|
|
20
|
+
} | {
|
|
21
|
+
readonly operator: "lt";
|
|
22
|
+
readonly value: T;
|
|
23
|
+
} | {
|
|
24
|
+
readonly operator: "lte";
|
|
25
|
+
readonly value: T;
|
|
26
|
+
} | {
|
|
27
|
+
readonly operator: "exists";
|
|
28
|
+
readonly value: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type StorageWhere<T extends object> = { readonly [K in keyof T]?: T[K] extends StorageScalar ? StorageComparison<T[K]> : StorageComparison<StorageScalar>; } & {
|
|
31
|
+
readonly and?: readonly StorageWhere<T>[];
|
|
32
|
+
readonly or?: readonly StorageWhere<T>[];
|
|
33
|
+
};
|
|
34
|
+
export type StorageOrder<T extends object> = {
|
|
35
|
+
readonly field: keyof T & string;
|
|
36
|
+
readonly direction: "asc" | "desc";
|
|
37
|
+
};
|
|
38
|
+
export type StorageQuery<T extends StorageRecord> = {
|
|
39
|
+
readonly where?: StorageWhere<T>;
|
|
40
|
+
readonly orderBy?: readonly StorageOrder<T>[];
|
|
41
|
+
readonly limit?: number;
|
|
42
|
+
readonly offset?: number;
|
|
43
|
+
};
|
|
44
|
+
export type StorageCursor = {
|
|
45
|
+
readonly value: string;
|
|
46
|
+
};
|
|
47
|
+
export type StorageCursorQuery<T extends StorageRecord> = Omit<StorageQuery<T>, "offset"> & {
|
|
48
|
+
readonly first: number;
|
|
49
|
+
readonly after?: StorageCursor;
|
|
50
|
+
};
|
|
51
|
+
export type StoragePage<T extends StorageRecord> = {
|
|
52
|
+
readonly items: readonly T[];
|
|
53
|
+
readonly nextCursor: StorageCursor | null;
|
|
54
|
+
readonly hasNextPage: boolean;
|
|
55
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StorageCollection, StorageRecord } from "./collection.types.cjs";
|
|
2
|
+
export type StorageSchemaRegistry = {
|
|
3
|
+
readonly collections: readonly StorageCollection<StorageRecord>[];
|
|
4
|
+
};
|
|
5
|
+
export type StorageIndexDefinition = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly collection: string;
|
|
8
|
+
readonly fields: readonly string[];
|
|
9
|
+
readonly unique?: boolean;
|
|
10
|
+
readonly expireAfterSeconds?: number;
|
|
11
|
+
};
|
|
12
|
+
export type StorageSchemaAdapter = {
|
|
13
|
+
ensureCollection<T extends StorageRecord>(collection: StorageCollection<T>): Promise<void>;
|
|
14
|
+
ensureIndex(index: StorageIndexDefinition): Promise<void>;
|
|
15
|
+
listIndexes(collection: string): Promise<readonly StorageIndexDefinition[]>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StorageCollection, StorageRecord } from "./collection.types.js";
|
|
2
|
+
export type StorageSchemaRegistry = {
|
|
3
|
+
readonly collections: readonly StorageCollection<StorageRecord>[];
|
|
4
|
+
};
|
|
5
|
+
export type StorageIndexDefinition = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly collection: string;
|
|
8
|
+
readonly fields: readonly string[];
|
|
9
|
+
readonly unique?: boolean;
|
|
10
|
+
readonly expireAfterSeconds?: number;
|
|
11
|
+
};
|
|
12
|
+
export type StorageSchemaAdapter = {
|
|
13
|
+
ensureCollection<T extends StorageRecord>(collection: StorageCollection<T>): Promise<void>;
|
|
14
|
+
ensureIndex(index: StorageIndexDefinition): Promise<void>;
|
|
15
|
+
listIndexes(collection: string): Promise<readonly StorageIndexDefinition[]>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const require_storage_errors = require("../errors/storage.errors.cjs");
|
|
2
|
+
// src/utils/query.utils.ts
|
|
3
|
+
const isScalar = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
|
|
4
|
+
const isComparison = (value) => {
|
|
5
|
+
if (!value || typeof value !== "object") return false;
|
|
6
|
+
const comparison = value;
|
|
7
|
+
if (![
|
|
8
|
+
"eq",
|
|
9
|
+
"ne",
|
|
10
|
+
"in",
|
|
11
|
+
"notIn",
|
|
12
|
+
"gt",
|
|
13
|
+
"gte",
|
|
14
|
+
"lt",
|
|
15
|
+
"lte",
|
|
16
|
+
"exists"
|
|
17
|
+
].includes(String(comparison.operator))) return false;
|
|
18
|
+
if (comparison.operator === "in" || comparison.operator === "notIn") return Array.isArray(comparison.value) && comparison.value.every(isScalar);
|
|
19
|
+
if (comparison.operator === "exists") return typeof comparison.value === "boolean";
|
|
20
|
+
return isScalar(comparison.value);
|
|
21
|
+
};
|
|
22
|
+
const validateWhere = (where) => {
|
|
23
|
+
for (const [field, value] of Object.entries(where)) {
|
|
24
|
+
if (field === "and" || field === "or") {
|
|
25
|
+
if (!Array.isArray(value)) throw new require_storage_errors.StorageValidationError(`Query operator "${field}" must be an array.`);
|
|
26
|
+
for (const nested of value) validateWhere(nested);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!isComparison(value)) throw new require_storage_errors.StorageValidationError(`Invalid comparison for field "${field}".`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const validateQuery = (query) => {
|
|
33
|
+
if (query.limit !== void 0) {
|
|
34
|
+
if (!Number.isInteger(query.limit) || query.limit < 0) throw new require_storage_errors.StorageValidationError("Query limit must be a non-negative integer.");
|
|
35
|
+
}
|
|
36
|
+
if (query.offset !== void 0) {
|
|
37
|
+
if (!Number.isInteger(query.offset) || query.offset < 0) throw new require_storage_errors.StorageValidationError("Query offset must be a non-negative integer.");
|
|
38
|
+
}
|
|
39
|
+
if (query.where) validateWhere(query.where);
|
|
40
|
+
};
|
|
41
|
+
exports.validateQuery = validateQuery;
|
|
42
|
+
exports.validateWhere = validateWhere;
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=query.utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.utils.cjs","names":["StorageValidationError"],"sources":["../../src/utils/query.utils.ts"],"sourcesContent":["import type { StorageRecord, StorageScalar } from \"../types/collection.types\";\nimport type {\n StorageComparison,\n StorageQuery,\n StorageWhere,\n} from \"../types/query.types\";\nimport { StorageValidationError } from \"../errors/storage.errors\";\n\nconst isScalar = (value: unknown): value is StorageScalar =>\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n value instanceof Date;\n\nconst isComparison = (value: unknown): value is StorageComparison => {\n if (!value || typeof value !== \"object\") return false;\n\n const comparison = value as Record<string, unknown>;\n const operators = [\n \"eq\",\n \"ne\",\n \"in\",\n \"notIn\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"exists\",\n ];\n\n if (!operators.includes(String(comparison.operator))) return false;\n\n if (comparison.operator === \"in\" || comparison.operator === \"notIn\") {\n return Array.isArray(comparison.value) && comparison.value.every(isScalar);\n }\n\n if (comparison.operator === \"exists\") {\n return typeof comparison.value === \"boolean\";\n }\n\n return isScalar(comparison.value);\n};\n\nexport const validateWhere = <T extends object>(\n where: StorageWhere<T>,\n): void => {\n for (const [field, value] of Object.entries(where)) {\n if (field === \"and\" || field === \"or\") {\n if (!Array.isArray(value)) {\n throw new StorageValidationError(\n `Query operator \"${field}\" must be an array.`,\n );\n }\n\n for (const nested of value) {\n validateWhere(nested as StorageWhere<T>);\n }\n\n continue;\n }\n\n if (!isComparison(value)) {\n throw new StorageValidationError(\n `Invalid comparison for field \"${field}\".`,\n );\n }\n }\n};\n\nexport const validateQuery = <T extends StorageRecord>(\n query: StorageQuery<T>,\n): void => {\n if (query.limit !== undefined) {\n if (!Number.isInteger(query.limit) || query.limit < 0) {\n throw new StorageValidationError(\n \"Query limit must be a non-negative integer.\",\n );\n }\n }\n\n if (query.offset !== undefined) {\n if (!Number.isInteger(query.offset) || query.offset < 0) {\n throw new StorageValidationError(\n \"Query offset must be a non-negative integer.\",\n );\n }\n }\n\n if (query.where) validateWhere(query.where);\n};\n"],"mappings":";;AAQA,MAAM,YAAY,UAChB,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,aACjB,iBAAiB;AAEnB,MAAM,gBAAgB,UAA+C;CACnE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAEhD,MAAM,aAAa;CAanB,IAAI,CAAC;EAXH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAGW,CAAC,CAAC,SAAS,OAAO,WAAW,QAAQ,CAAC,GAAG,OAAO;CAE7D,IAAI,WAAW,aAAa,QAAQ,WAAW,aAAa,SAC1D,OAAO,MAAM,QAAQ,WAAW,KAAK,KAAK,WAAW,MAAM,MAAM,QAAQ;CAG3E,IAAI,WAAW,aAAa,UAC1B,OAAO,OAAO,WAAW,UAAU;CAGrC,OAAO,SAAS,WAAW,KAAK;AAClC;AAEA,MAAa,iBACX,UACS;CACT,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,GAAG;EAClD,IAAI,UAAU,SAAS,UAAU,MAAM;GACrC,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,MAAM,IAAIA,uBAAAA,uBACR,mBAAmB,MAAM,oBAC3B;GAGF,KAAK,MAAM,UAAU,OACnB,cAAc,MAAyB;GAGzC;EACF;EAEA,IAAI,CAAC,aAAa,KAAK,GACrB,MAAM,IAAIA,uBAAAA,uBACR,iCAAiC,MAAM,GACzC;CAEJ;AACF;AAEA,MAAa,iBACX,UACS;CACT,IAAI,MAAM,UAAU,KAAA,GACd;MAAA,CAAC,OAAO,UAAU,MAAM,KAAK,KAAK,MAAM,QAAQ,GAClD,MAAM,IAAIA,uBAAAA,uBACR,6CACF;CAAA;CAIJ,IAAI,MAAM,WAAW,KAAA,GACf;MAAA,CAAC,OAAO,UAAU,MAAM,MAAM,KAAK,MAAM,SAAS,GACpD,MAAM,IAAIA,uBAAAA,uBACR,8CACF;CAAA;CAIJ,IAAI,MAAM,OAAO,cAAc,MAAM,KAAK;AAC5C"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StorageRecord } from "../types/collection.types.cjs";
|
|
2
|
+
import { StorageQuery, StorageWhere } from "../types/query.types.cjs";
|
|
3
|
+
export declare const validateWhere: <T extends object>(where: StorageWhere<T>) => void;
|
|
4
|
+
export declare const validateQuery: <T extends StorageRecord>(query: StorageQuery<T>) => void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StorageRecord } from "../types/collection.types.js";
|
|
2
|
+
import { StorageQuery, StorageWhere } from "../types/query.types.js";
|
|
3
|
+
export declare const validateWhere: <T extends object>(where: StorageWhere<T>) => void;
|
|
4
|
+
export declare const validateQuery: <T extends StorageRecord>(query: StorageQuery<T>) => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StorageValidationError } from "../errors/storage.errors.js";
|
|
2
|
+
// src/utils/query.utils.ts
|
|
3
|
+
const isScalar = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
|
|
4
|
+
const isComparison = (value) => {
|
|
5
|
+
if (!value || typeof value !== "object") return false;
|
|
6
|
+
const comparison = value;
|
|
7
|
+
if (![
|
|
8
|
+
"eq",
|
|
9
|
+
"ne",
|
|
10
|
+
"in",
|
|
11
|
+
"notIn",
|
|
12
|
+
"gt",
|
|
13
|
+
"gte",
|
|
14
|
+
"lt",
|
|
15
|
+
"lte",
|
|
16
|
+
"exists"
|
|
17
|
+
].includes(String(comparison.operator))) return false;
|
|
18
|
+
if (comparison.operator === "in" || comparison.operator === "notIn") return Array.isArray(comparison.value) && comparison.value.every(isScalar);
|
|
19
|
+
if (comparison.operator === "exists") return typeof comparison.value === "boolean";
|
|
20
|
+
return isScalar(comparison.value);
|
|
21
|
+
};
|
|
22
|
+
const validateWhere = (where) => {
|
|
23
|
+
for (const [field, value] of Object.entries(where)) {
|
|
24
|
+
if (field === "and" || field === "or") {
|
|
25
|
+
if (!Array.isArray(value)) throw new StorageValidationError(`Query operator "${field}" must be an array.`);
|
|
26
|
+
for (const nested of value) validateWhere(nested);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!isComparison(value)) throw new StorageValidationError(`Invalid comparison for field "${field}".`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const validateQuery = (query) => {
|
|
33
|
+
if (query.limit !== void 0) {
|
|
34
|
+
if (!Number.isInteger(query.limit) || query.limit < 0) throw new StorageValidationError("Query limit must be a non-negative integer.");
|
|
35
|
+
}
|
|
36
|
+
if (query.offset !== void 0) {
|
|
37
|
+
if (!Number.isInteger(query.offset) || query.offset < 0) throw new StorageValidationError("Query offset must be a non-negative integer.");
|
|
38
|
+
}
|
|
39
|
+
if (query.where) validateWhere(query.where);
|
|
40
|
+
};
|
|
41
|
+
export { validateQuery, validateWhere };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=query.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.utils.js","names":[],"sources":["../../src/utils/query.utils.ts"],"sourcesContent":["import type { StorageRecord, StorageScalar } from \"../types/collection.types\";\nimport type {\n StorageComparison,\n StorageQuery,\n StorageWhere,\n} from \"../types/query.types\";\nimport { StorageValidationError } from \"../errors/storage.errors\";\n\nconst isScalar = (value: unknown): value is StorageScalar =>\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n value instanceof Date;\n\nconst isComparison = (value: unknown): value is StorageComparison => {\n if (!value || typeof value !== \"object\") return false;\n\n const comparison = value as Record<string, unknown>;\n const operators = [\n \"eq\",\n \"ne\",\n \"in\",\n \"notIn\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"exists\",\n ];\n\n if (!operators.includes(String(comparison.operator))) return false;\n\n if (comparison.operator === \"in\" || comparison.operator === \"notIn\") {\n return Array.isArray(comparison.value) && comparison.value.every(isScalar);\n }\n\n if (comparison.operator === \"exists\") {\n return typeof comparison.value === \"boolean\";\n }\n\n return isScalar(comparison.value);\n};\n\nexport const validateWhere = <T extends object>(\n where: StorageWhere<T>,\n): void => {\n for (const [field, value] of Object.entries(where)) {\n if (field === \"and\" || field === \"or\") {\n if (!Array.isArray(value)) {\n throw new StorageValidationError(\n `Query operator \"${field}\" must be an array.`,\n );\n }\n\n for (const nested of value) {\n validateWhere(nested as StorageWhere<T>);\n }\n\n continue;\n }\n\n if (!isComparison(value)) {\n throw new StorageValidationError(\n `Invalid comparison for field \"${field}\".`,\n );\n }\n }\n};\n\nexport const validateQuery = <T extends StorageRecord>(\n query: StorageQuery<T>,\n): void => {\n if (query.limit !== undefined) {\n if (!Number.isInteger(query.limit) || query.limit < 0) {\n throw new StorageValidationError(\n \"Query limit must be a non-negative integer.\",\n );\n }\n }\n\n if (query.offset !== undefined) {\n if (!Number.isInteger(query.offset) || query.offset < 0) {\n throw new StorageValidationError(\n \"Query offset must be a non-negative integer.\",\n );\n }\n }\n\n if (query.where) validateWhere(query.where);\n};\n"],"mappings":";;AAQA,MAAM,YAAY,UAChB,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,aACjB,iBAAiB;AAEnB,MAAM,gBAAgB,UAA+C;CACnE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAEhD,MAAM,aAAa;CAanB,IAAI,CAAC;EAXH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAGW,CAAC,CAAC,SAAS,OAAO,WAAW,QAAQ,CAAC,GAAG,OAAO;CAE7D,IAAI,WAAW,aAAa,QAAQ,WAAW,aAAa,SAC1D,OAAO,MAAM,QAAQ,WAAW,KAAK,KAAK,WAAW,MAAM,MAAM,QAAQ;CAG3E,IAAI,WAAW,aAAa,UAC1B,OAAO,OAAO,WAAW,UAAU;CAGrC,OAAO,SAAS,WAAW,KAAK;AAClC;AAEA,MAAa,iBACX,UACS;CACT,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,KAAK,GAAG;EAClD,IAAI,UAAU,SAAS,UAAU,MAAM;GACrC,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,MAAM,IAAI,uBACR,mBAAmB,MAAM,oBAC3B;GAGF,KAAK,MAAM,UAAU,OACnB,cAAc,MAAyB;GAGzC;EACF;EAEA,IAAI,CAAC,aAAa,KAAK,GACrB,MAAM,IAAI,uBACR,iCAAiC,MAAM,GACzC;CAEJ;AACF;AAEA,MAAa,iBACX,UACS;CACT,IAAI,MAAM,UAAU,KAAA,GACd;MAAA,CAAC,OAAO,UAAU,MAAM,KAAK,KAAK,MAAM,QAAQ,GAClD,MAAM,IAAI,uBACR,6CACF;CAAA;CAIJ,IAAI,MAAM,WAAW,KAAA,GACf;MAAA,CAAC,OAAO,UAAU,MAAM,MAAM,KAAK,MAAM,SAAS,GACpD,MAAM,IAAI,uBACR,8CACF;CAAA;CAIJ,IAAI,MAAM,OAAO,cAAc,MAAM,KAAK;AAC5C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puzzle-ui/storage",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Vendor-neutral storage contracts for Puzzle Auth.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"./testing": {
|
|
26
|
+
"types": "./dist/testing/index.d.ts",
|
|
27
|
+
"import": "./dist/testing/index.js",
|
|
28
|
+
"require": "./dist/testing/index.cjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown",
|
|
37
|
+
"typecheck": "tsc --noEmit -p .",
|
|
38
|
+
"vitest": "node --test",
|
|
39
|
+
"clean": "rm -rf dist",
|
|
40
|
+
"prepack": "tsdown"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/bun": "^1.4.4",
|
|
44
|
+
"bun": "^1.4.4",
|
|
45
|
+
"tsdown": "^0.23.1",
|
|
46
|
+
"typescript": "^7.0.2"
|
|
47
|
+
}
|
|
48
|
+
}
|