@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.
Files changed (35) hide show
  1. package/README.md +118 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/errors/storage.errors.cjs +55 -0
  4. package/dist/errors/storage.errors.cjs.map +1 -0
  5. package/dist/errors/storage.errors.d.cts +27 -0
  6. package/dist/errors/storage.errors.d.ts +27 -0
  7. package/dist/errors/storage.errors.js +50 -0
  8. package/dist/errors/storage.errors.js.map +1 -0
  9. package/dist/index.cjs +13 -0
  10. package/dist/index.d.cts +9 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.js +4 -0
  13. package/dist/types/adapter.types.d.cts +68 -0
  14. package/dist/types/adapter.types.d.ts +68 -0
  15. package/dist/types/capabilities.types.cjs +14 -0
  16. package/dist/types/capabilities.types.cjs.map +1 -0
  17. package/dist/types/capabilities.types.d.cts +16 -0
  18. package/dist/types/capabilities.types.d.ts +16 -0
  19. package/dist/types/capabilities.types.js +14 -0
  20. package/dist/types/capabilities.types.js.map +1 -0
  21. package/dist/types/collection.types.d.cts +25 -0
  22. package/dist/types/collection.types.d.ts +25 -0
  23. package/dist/types/migration.types.d.cts +53 -0
  24. package/dist/types/migration.types.d.ts +53 -0
  25. package/dist/types/query.types.d.cts +55 -0
  26. package/dist/types/query.types.d.ts +55 -0
  27. package/dist/types/schema.types.d.cts +16 -0
  28. package/dist/types/schema.types.d.ts +16 -0
  29. package/dist/utils/query.utils.cjs +44 -0
  30. package/dist/utils/query.utils.cjs.map +1 -0
  31. package/dist/utils/query.utils.d.cts +4 -0
  32. package/dist/utils/query.utils.d.ts +4 -0
  33. package/dist/utils/query.utils.js +43 -0
  34. package/dist/utils/query.utils.js.map +1 -0
  35. package/package.json +46 -4
package/README.md CHANGED
@@ -1,3 +1,119 @@
1
- # Temporary Holding Version
1
+ # @puzzle-ui/storage
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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;
@@ -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 };
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }