@frostpillar/frostpillar-storage-engine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README-JA.md +1205 -0
  3. package/README.md +1204 -0
  4. package/dist/drivers/file.cjs +960 -0
  5. package/dist/drivers/file.d.ts +3 -0
  6. package/dist/drivers/file.js +18 -0
  7. package/dist/drivers/indexedDB.cjs +570 -0
  8. package/dist/drivers/indexedDB.d.ts +3 -0
  9. package/dist/drivers/indexedDB.js +18 -0
  10. package/dist/drivers/localStorage.cjs +668 -0
  11. package/dist/drivers/localStorage.d.ts +3 -0
  12. package/dist/drivers/localStorage.js +23 -0
  13. package/dist/drivers/opfs.cjs +550 -0
  14. package/dist/drivers/opfs.d.ts +3 -0
  15. package/dist/drivers/opfs.js +18 -0
  16. package/dist/drivers/syncStorage.cjs +898 -0
  17. package/dist/drivers/syncStorage.d.ts +3 -0
  18. package/dist/drivers/syncStorage.js +22 -0
  19. package/dist/drivers/validation.d.ts +1 -0
  20. package/dist/drivers/validation.js +8 -0
  21. package/dist/errors/index.d.ts +32 -0
  22. package/dist/errors/index.js +48 -0
  23. package/dist/frostpillar-storage-engine.min.js +1 -0
  24. package/dist/index.cjs +2957 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +6 -0
  27. package/dist/storage/backend/asyncDurableAutoCommitController.d.ts +26 -0
  28. package/dist/storage/backend/asyncDurableAutoCommitController.js +188 -0
  29. package/dist/storage/backend/asyncMutex.d.ts +7 -0
  30. package/dist/storage/backend/asyncMutex.js +38 -0
  31. package/dist/storage/backend/autoCommit.d.ts +2 -0
  32. package/dist/storage/backend/autoCommit.js +22 -0
  33. package/dist/storage/backend/capacity.d.ts +2 -0
  34. package/dist/storage/backend/capacity.js +27 -0
  35. package/dist/storage/backend/capacityResolver.d.ts +3 -0
  36. package/dist/storage/backend/capacityResolver.js +25 -0
  37. package/dist/storage/backend/encoding.d.ts +17 -0
  38. package/dist/storage/backend/encoding.js +148 -0
  39. package/dist/storage/backend/types.d.ts +184 -0
  40. package/dist/storage/backend/types.js +1 -0
  41. package/dist/storage/btree/recordKeyIndexBTree.d.ts +39 -0
  42. package/dist/storage/btree/recordKeyIndexBTree.js +104 -0
  43. package/dist/storage/config/config.browser.d.ts +4 -0
  44. package/dist/storage/config/config.browser.js +8 -0
  45. package/dist/storage/config/config.d.ts +1 -0
  46. package/dist/storage/config/config.js +1 -0
  47. package/dist/storage/config/config.node.d.ts +4 -0
  48. package/dist/storage/config/config.node.js +74 -0
  49. package/dist/storage/config/config.shared.d.ts +6 -0
  50. package/dist/storage/config/config.shared.js +105 -0
  51. package/dist/storage/datastore/Datastore.d.ts +47 -0
  52. package/dist/storage/datastore/Datastore.js +525 -0
  53. package/dist/storage/datastore/datastoreClose.d.ts +12 -0
  54. package/dist/storage/datastore/datastoreClose.js +60 -0
  55. package/dist/storage/datastore/datastoreKeyDefinition.d.ts +7 -0
  56. package/dist/storage/datastore/datastoreKeyDefinition.js +60 -0
  57. package/dist/storage/datastore/datastoreLifecycle.d.ts +18 -0
  58. package/dist/storage/datastore/datastoreLifecycle.js +63 -0
  59. package/dist/storage/datastore/mutationById.d.ts +29 -0
  60. package/dist/storage/datastore/mutationById.js +71 -0
  61. package/dist/storage/drivers/IndexedDB/indexedDBBackend.d.ts +11 -0
  62. package/dist/storage/drivers/IndexedDB/indexedDBBackend.js +109 -0
  63. package/dist/storage/drivers/IndexedDB/indexedDBBackendController.d.ts +27 -0
  64. package/dist/storage/drivers/IndexedDB/indexedDBBackendController.js +60 -0
  65. package/dist/storage/drivers/IndexedDB/indexedDBConfig.d.ts +7 -0
  66. package/dist/storage/drivers/IndexedDB/indexedDBConfig.js +24 -0
  67. package/dist/storage/drivers/file/fileBackend.d.ts +5 -0
  68. package/dist/storage/drivers/file/fileBackend.js +168 -0
  69. package/dist/storage/drivers/file/fileBackendController.d.ts +31 -0
  70. package/dist/storage/drivers/file/fileBackendController.js +72 -0
  71. package/dist/storage/drivers/file/fileBackendSnapshot.d.ts +10 -0
  72. package/dist/storage/drivers/file/fileBackendSnapshot.js +166 -0
  73. package/dist/storage/drivers/localStorage/localStorageBackend.d.ts +10 -0
  74. package/dist/storage/drivers/localStorage/localStorageBackend.js +156 -0
  75. package/dist/storage/drivers/localStorage/localStorageBackendController.d.ts +24 -0
  76. package/dist/storage/drivers/localStorage/localStorageBackendController.js +35 -0
  77. package/dist/storage/drivers/localStorage/localStorageConfig.d.ts +10 -0
  78. package/dist/storage/drivers/localStorage/localStorageConfig.js +16 -0
  79. package/dist/storage/drivers/localStorage/localStorageLayout.d.ts +5 -0
  80. package/dist/storage/drivers/localStorage/localStorageLayout.js +29 -0
  81. package/dist/storage/drivers/opfs/opfsBackend.d.ts +12 -0
  82. package/dist/storage/drivers/opfs/opfsBackend.js +142 -0
  83. package/dist/storage/drivers/opfs/opfsBackendController.d.ts +26 -0
  84. package/dist/storage/drivers/opfs/opfsBackendController.js +44 -0
  85. package/dist/storage/drivers/syncStorage/syncStorageAdapter.d.ts +2 -0
  86. package/dist/storage/drivers/syncStorage/syncStorageAdapter.js +123 -0
  87. package/dist/storage/drivers/syncStorage/syncStorageBackend.d.ts +11 -0
  88. package/dist/storage/drivers/syncStorage/syncStorageBackend.js +169 -0
  89. package/dist/storage/drivers/syncStorage/syncStorageBackendController.d.ts +24 -0
  90. package/dist/storage/drivers/syncStorage/syncStorageBackendController.js +34 -0
  91. package/dist/storage/drivers/syncStorage/syncStorageChunkMaintenance.d.ts +2 -0
  92. package/dist/storage/drivers/syncStorage/syncStorageChunkMaintenance.js +28 -0
  93. package/dist/storage/drivers/syncStorage/syncStorageConfig.d.ts +13 -0
  94. package/dist/storage/drivers/syncStorage/syncStorageConfig.js +42 -0
  95. package/dist/storage/drivers/syncStorage/syncStorageQuota.d.ts +3 -0
  96. package/dist/storage/drivers/syncStorage/syncStorageQuota.js +45 -0
  97. package/dist/storage/record/ordering.d.ts +3 -0
  98. package/dist/storage/record/ordering.js +7 -0
  99. package/dist/types.d.ts +125 -0
  100. package/dist/types.js +1 -0
  101. package/dist/validation/metadata.d.ts +1 -0
  102. package/dist/validation/metadata.js +7 -0
  103. package/dist/validation/payload.d.ts +7 -0
  104. package/dist/validation/payload.js +135 -0
  105. package/dist/validation/typeGuards.d.ts +1 -0
  106. package/dist/validation/typeGuards.js +7 -0
  107. package/package.json +110 -0
@@ -0,0 +1,3 @@
1
+ import type { SyncStorageBackendState, SyncStorageManifest } from '../../backend/types.js';
2
+ export declare const isQuotaBrowserError: (error: unknown) => boolean;
3
+ export declare const validateSyncStorageCommitQuota: (state: SyncStorageBackendState, generation: number, chunks: string[], manifest: SyncStorageManifest, resolveChunkKey: (generation: number, index: number) => string, manifestStorageKey: string) => void;
@@ -0,0 +1,45 @@
1
+ import { QuotaExceededError, StorageEngineError, } from '../../../errors/index.js';
2
+ const utf8Encoder = new TextEncoder();
3
+ const computeSyncStorageItemBytes = (key, value) => {
4
+ const valueJson = JSON.stringify(value);
5
+ if (valueJson === undefined) {
6
+ throw new StorageEngineError(`syncStorage value for key "${key}" cannot be serialized.`);
7
+ }
8
+ return (utf8Encoder.encode(key).byteLength + utf8Encoder.encode(valueJson).byteLength);
9
+ };
10
+ export const isQuotaBrowserError = (error) => {
11
+ if (error instanceof QuotaExceededError) {
12
+ return true;
13
+ }
14
+ if (!(error instanceof Error)) {
15
+ return false;
16
+ }
17
+ const normalized = `${error.name}:${error.message}`;
18
+ return /quota|max_items|quota_bytes|quota_bytes_per_item/i.test(normalized);
19
+ };
20
+ export const validateSyncStorageCommitQuota = (state, generation, chunks, manifest, resolveChunkKey, manifestStorageKey) => {
21
+ const pendingItems = chunks.map((chunkValue, chunkIndex) => {
22
+ return {
23
+ key: resolveChunkKey(generation, chunkIndex),
24
+ value: chunkValue,
25
+ };
26
+ });
27
+ pendingItems.push({
28
+ key: manifestStorageKey,
29
+ value: manifest,
30
+ });
31
+ if (pendingItems.length > state.maxItems) {
32
+ throw new QuotaExceededError(`syncStorage snapshot requires ${pendingItems.length} items but maxItems is ${state.maxItems}.`);
33
+ }
34
+ let totalBytes = 0;
35
+ for (const pendingItem of pendingItems) {
36
+ const itemBytes = computeSyncStorageItemBytes(pendingItem.key, pendingItem.value);
37
+ if (itemBytes > state.maxItemBytes) {
38
+ throw new QuotaExceededError(`syncStorage item "${pendingItem.key}" requires ${itemBytes} bytes but maxItemBytes is ${state.maxItemBytes}.`);
39
+ }
40
+ totalBytes += itemBytes;
41
+ }
42
+ if (totalBytes > state.maxTotalBytes) {
43
+ throw new QuotaExceededError(`syncStorage snapshot requires ${totalBytes} bytes but maxTotalBytes is ${state.maxTotalBytes}.`);
44
+ }
45
+ };
@@ -0,0 +1,3 @@
1
+ import type { KeyedRecord, PersistedRecord } from '../../types.js';
2
+ import type { EntryId } from '../btree/recordKeyIndexBTree.js';
3
+ export declare const toPublicRecord: (entryId: EntryId, key: unknown, record: PersistedRecord) => KeyedRecord<unknown>;
@@ -0,0 +1,7 @@
1
+ export const toPublicRecord = (entryId, key, record) => {
2
+ return {
3
+ _id: entryId,
4
+ key,
5
+ payload: record.payload,
6
+ };
7
+ };
@@ -0,0 +1,125 @@
1
+ import type { BTreeJSON, EntryId } from '@frostpillar/frostpillar-btree';
2
+ export type { BTreeJSON, EntryId };
3
+ export type ByteSizeInput = number | `${number}B` | `${number}KB` | `${number}MB` | `${number}GB` | 'backendLimit';
4
+ export type AutoCommitFrequencyInput = 'immediate' | number | `${number}ms` | `${number}s` | `${number}m` | `${number}h`;
5
+ export type SupportedValue = string | number | boolean | null;
6
+ export type SupportedNestedValue = SupportedValue | {
7
+ [key in string]: SupportedNestedValue;
8
+ };
9
+ export type RecordPayload = {
10
+ [key in string]: SupportedNestedValue;
11
+ };
12
+ export interface DatastoreKeyDefinition<TKey = unknown, TInput = TKey> {
13
+ normalize: (value: TInput, fieldName: string) => TKey;
14
+ compare: (left: TKey, right: TKey) => number;
15
+ serialize: (key: TKey) => string;
16
+ deserialize: (serialized: string) => TKey;
17
+ }
18
+ export interface KeyedRecord<TKey = unknown> {
19
+ readonly _id: EntryId;
20
+ readonly key: TKey;
21
+ readonly payload: RecordPayload;
22
+ }
23
+ export interface PersistedRecord {
24
+ payload: RecordPayload;
25
+ sizeBytes: number;
26
+ }
27
+ export interface InputRecord<TKeyInput = unknown> {
28
+ key: TKeyInput;
29
+ payload: RecordPayload;
30
+ }
31
+ export interface KeyRangeQuery<TKeyInput = unknown> {
32
+ start: TKeyInput;
33
+ end: TKeyInput;
34
+ }
35
+ export type CapacityPolicy = 'strict' | 'turnover';
36
+ export type DuplicateKeyPolicy = 'allow' | 'replace' | 'reject';
37
+ export interface CapacityConfig {
38
+ maxSize: ByteSizeInput;
39
+ policy?: CapacityPolicy;
40
+ }
41
+ export interface AutoCommitConfig {
42
+ frequency?: AutoCommitFrequencyInput;
43
+ maxPendingBytes?: number;
44
+ }
45
+ export interface DatastoreCommonConfig {
46
+ key?: DatastoreKeyDefinition<unknown, unknown>;
47
+ capacity?: CapacityConfig;
48
+ autoCommit?: AutoCommitConfig;
49
+ duplicateKeys?: DuplicateKeyPolicy;
50
+ skipPayloadValidation?: boolean;
51
+ }
52
+ export interface FileTargetByPathConfig {
53
+ kind: 'path';
54
+ filePath: string;
55
+ directory?: never;
56
+ fileName?: never;
57
+ filePrefix?: never;
58
+ }
59
+ export interface FileTargetByDirectoryConfig {
60
+ kind: 'directory';
61
+ directory: string;
62
+ fileName?: string;
63
+ filePrefix?: string;
64
+ filePath?: never;
65
+ }
66
+ export type FileTargetConfig = FileTargetByPathConfig | FileTargetByDirectoryConfig;
67
+ export interface FileBackendConfig {
68
+ target?: FileTargetConfig;
69
+ filePath?: string;
70
+ }
71
+ export interface OpfsConfig {
72
+ directoryName?: string;
73
+ }
74
+ export interface IndexedDBConfig {
75
+ databaseName?: string;
76
+ objectStoreName?: string;
77
+ version?: number;
78
+ }
79
+ export interface LocalStorageConfig {
80
+ keyPrefix?: string;
81
+ databaseKey?: string;
82
+ maxChunkChars?: number;
83
+ maxChunks?: number;
84
+ }
85
+ export interface SyncStorageConfig {
86
+ keyPrefix?: string;
87
+ databaseKey?: string;
88
+ maxChunkChars?: number;
89
+ maxChunks?: number;
90
+ maxItemBytes?: number;
91
+ maxTotalBytes?: number;
92
+ maxItems?: number;
93
+ }
94
+ export interface DatastoreDriverSnapshot {
95
+ treeJSON: BTreeJSON<unknown, unknown>;
96
+ }
97
+ export interface DatastoreDriverController {
98
+ handleRecordAppended(encodedBytes: number): Promise<void>;
99
+ handleCleared(): Promise<void>;
100
+ commitNow(): Promise<void>;
101
+ close(): Promise<void>;
102
+ }
103
+ export interface DatastoreDriverInitResult {
104
+ controller: DatastoreDriverController;
105
+ initialTreeJSON: BTreeJSON<unknown, unknown> | null;
106
+ initialCurrentSizeBytes: number;
107
+ }
108
+ export interface DatastoreDriverInitContext {
109
+ getSnapshot: () => DatastoreDriverSnapshot;
110
+ autoCommit?: AutoCommitConfig;
111
+ onAutoCommitError: (error: unknown) => void;
112
+ }
113
+ export interface DatastoreDriver {
114
+ init(context: DatastoreDriverInitContext): DatastoreDriverInitResult | Promise<DatastoreDriverInitResult>;
115
+ resolveBackendLimitBytes?: () => number;
116
+ }
117
+ export interface DatastoreConfig extends DatastoreCommonConfig {
118
+ driver?: DatastoreDriver;
119
+ }
120
+ export interface DatastoreErrorEvent {
121
+ source: 'autoCommit';
122
+ error: Error;
123
+ occurredAt: number;
124
+ }
125
+ export type DatastoreErrorListener = (event: DatastoreErrorEvent) => void | Promise<void>;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const parseNonNegativeSafeInteger: (value: unknown, fieldName: string, backendName: string) => number;
@@ -0,0 +1,7 @@
1
+ import { StorageEngineError } from '../errors/index.js';
2
+ export const parseNonNegativeSafeInteger = (value, fieldName, backendName) => {
3
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
4
+ throw new StorageEngineError(`${backendName} ${fieldName} must be a non-negative safe integer.`);
5
+ }
6
+ return value;
7
+ };
@@ -0,0 +1,7 @@
1
+ import type { RecordPayload } from '../types.js';
2
+ export interface PayloadValidationResult {
3
+ payload: RecordPayload;
4
+ sizeBytes: number;
5
+ }
6
+ export declare const deepFreezePayload: (payload: RecordPayload) => RecordPayload;
7
+ export declare const validateAndNormalizePayload: (payload: unknown) => PayloadValidationResult;
@@ -0,0 +1,135 @@
1
+ import { ValidationError } from '../errors/index.js';
2
+ import { computeUtf8ByteLength, estimateJsonStringBytes } from '../storage/backend/encoding.js';
3
+ const MAX_PAYLOAD_DEPTH = 64;
4
+ const MAX_PAYLOAD_KEY_BYTES = 1024;
5
+ const MAX_PAYLOAD_STRING_BYTES = 65535;
6
+ const MAX_PAYLOAD_KEYS_PER_OBJECT = 256;
7
+ const MAX_PAYLOAD_KEYS_TOTAL = 4096;
8
+ const MAX_PAYLOAD_TOTAL_BYTES = 1048576;
9
+ // JSON-aware byte estimates for non-string primitives.
10
+ const NULL_ESTIMATION_BYTES = 4; // "null" = 4 bytes
11
+ // JSON structural overhead constants for size estimation.
12
+ const JSON_KEY_COLON_OVERHEAD = 1; // 1 colon per key (quotes handled by estimateJsonStringBytes)
13
+ const JSON_OBJECT_BRACE_OVERHEAD = 2; // {} per object
14
+ // Root [key, {"payload": ...}] wrapper: [ + , + {"payload": + } + ] = 15 bytes
15
+ const JSON_ROOT_WRAPPER_OVERHEAD = 15;
16
+ const isPlainObject = (value) => {
17
+ if (typeof value !== 'object' || value === null) {
18
+ return false;
19
+ }
20
+ if (Array.isArray(value)) {
21
+ return false;
22
+ }
23
+ const objectValue = value;
24
+ const prototype = Object.getPrototypeOf(objectValue);
25
+ return prototype === Object.prototype || prototype === null;
26
+ };
27
+ const addValidationBytes = (state, bytes) => {
28
+ state.totalValidationBytes += bytes;
29
+ if (state.totalValidationBytes > MAX_PAYLOAD_TOTAL_BYTES) {
30
+ throw new ValidationError(`Payload aggregate validation bytes must be <= ${MAX_PAYLOAD_TOTAL_BYTES}.`);
31
+ }
32
+ };
33
+ const validatePayloadKey = (key, state) => {
34
+ if (key.trim().length === 0) {
35
+ throw new ValidationError('Payload keys must be non-empty strings.');
36
+ }
37
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
38
+ throw new ValidationError(`Payload key "${key}" is reserved and not allowed.`);
39
+ }
40
+ const keyBytes = computeUtf8ByteLength(key);
41
+ if (keyBytes > MAX_PAYLOAD_KEY_BYTES) {
42
+ throw new ValidationError(`Payload key UTF-8 byte length must be <= ${MAX_PAYLOAD_KEY_BYTES}.`);
43
+ }
44
+ state.totalKeyCount += 1;
45
+ if (state.totalKeyCount > MAX_PAYLOAD_KEYS_TOTAL) {
46
+ throw new ValidationError(`Payload total key count must be <= ${MAX_PAYLOAD_KEYS_TOTAL}.`);
47
+ }
48
+ // Add key as JSON string (with escaping + quotes) plus colon.
49
+ addValidationBytes(state, estimateJsonStringBytes(key) + JSON_KEY_COLON_OVERHEAD);
50
+ };
51
+ export const deepFreezePayload = (payload) => {
52
+ Object.freeze(payload);
53
+ for (const value of Object.values(payload)) {
54
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
55
+ deepFreezePayload(value);
56
+ }
57
+ }
58
+ return payload;
59
+ };
60
+ const validateAndCloneValue = (value, depth, state) => {
61
+ if (value === null) {
62
+ addValidationBytes(state, NULL_ESTIMATION_BYTES);
63
+ return null;
64
+ }
65
+ if (typeof value === 'string') {
66
+ const stringBytes = computeUtf8ByteLength(value);
67
+ if (stringBytes > MAX_PAYLOAD_STRING_BYTES) {
68
+ throw new ValidationError(`Payload string UTF-8 byte length must be <= ${MAX_PAYLOAD_STRING_BYTES}.`);
69
+ }
70
+ addValidationBytes(state, estimateJsonStringBytes(value));
71
+ return value;
72
+ }
73
+ if (typeof value === 'number') {
74
+ if (!Number.isFinite(value)) {
75
+ throw new ValidationError('Payload number values must be finite.');
76
+ }
77
+ addValidationBytes(state, String(value).length);
78
+ return value;
79
+ }
80
+ if (typeof value === 'boolean') {
81
+ addValidationBytes(state, value ? 4 : 5);
82
+ return value;
83
+ }
84
+ if (typeof value === 'bigint') {
85
+ throw new ValidationError('Payload bigint values are not supported.');
86
+ }
87
+ if (typeof value === 'object') {
88
+ if (Array.isArray(value)) {
89
+ throw new ValidationError('Payload arrays are not supported.');
90
+ }
91
+ if (!isPlainObject(value)) {
92
+ throw new ValidationError('Payload values must be plain objects.');
93
+ }
94
+ return validateAndClonePayloadObject(value, depth + 1, state);
95
+ }
96
+ throw new ValidationError('Payload values must be string | number | boolean | null or nested object.');
97
+ };
98
+ const validateAndClonePayloadObject = (payloadObject, depth, state) => {
99
+ const objectLevel = depth + 1;
100
+ if (objectLevel > MAX_PAYLOAD_DEPTH) {
101
+ throw new ValidationError(`Payload nesting depth must be <= ${MAX_PAYLOAD_DEPTH}.`);
102
+ }
103
+ if (state.activePath.has(payloadObject)) {
104
+ throw new ValidationError('Circular payload references are not supported.');
105
+ }
106
+ const entries = Object.entries(payloadObject);
107
+ if (entries.length > MAX_PAYLOAD_KEYS_PER_OBJECT) {
108
+ throw new ValidationError(`Payload object key count must be <= ${MAX_PAYLOAD_KEYS_PER_OBJECT}.`);
109
+ }
110
+ state.activePath.add(payloadObject);
111
+ // JSON structural overhead: 2 bytes for braces + (N-1) bytes for comma separators.
112
+ const entryCount = entries.length;
113
+ const commaBytes = entryCount > 1 ? entryCount - 1 : 0;
114
+ addValidationBytes(state, JSON_OBJECT_BRACE_OVERHEAD + commaBytes);
115
+ const copied = {};
116
+ for (const [key, value] of entries) {
117
+ validatePayloadKey(key, state);
118
+ copied[key] = validateAndCloneValue(value, depth, state);
119
+ }
120
+ state.activePath.delete(payloadObject);
121
+ return copied;
122
+ };
123
+ export const validateAndNormalizePayload = (payload) => {
124
+ if (!isPlainObject(payload)) {
125
+ throw new ValidationError('payload must be a non-null plain object.');
126
+ }
127
+ const state = {
128
+ activePath: new WeakSet(),
129
+ totalKeyCount: 0,
130
+ totalValidationBytes: 0,
131
+ };
132
+ const cloned = validateAndClonePayloadObject(payload, 0, state);
133
+ const sizeBytes = state.totalValidationBytes + JSON_ROOT_WRAPPER_OVERHEAD;
134
+ return { payload: cloned, sizeBytes };
135
+ };
@@ -0,0 +1 @@
1
+ export declare const isRecordObject: (value: unknown) => value is Record<string, unknown>;
@@ -0,0 +1,7 @@
1
+ export const isRecordObject = (value) => {
2
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
3
+ return false;
4
+ }
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ };
package/package.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "@frostpillar/frostpillar-storage-engine",
3
+ "version": "0.0.1",
4
+ "description": "Chunk-based storage engine for browsers and Node.js that packs many small key-value entries into a single backing store.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.cjs",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./drivers/file": {
15
+ "types": "./dist/drivers/file.d.ts",
16
+ "require": "./dist/drivers/file.cjs",
17
+ "default": "./dist/drivers/file.js"
18
+ },
19
+ "./drivers/localStorage": {
20
+ "types": "./dist/drivers/localStorage.d.ts",
21
+ "require": "./dist/drivers/localStorage.cjs",
22
+ "default": "./dist/drivers/localStorage.js"
23
+ },
24
+ "./drivers/indexedDB": {
25
+ "types": "./dist/drivers/indexedDB.d.ts",
26
+ "require": "./dist/drivers/indexedDB.cjs",
27
+ "default": "./dist/drivers/indexedDB.js"
28
+ },
29
+ "./drivers/opfs": {
30
+ "types": "./dist/drivers/opfs.d.ts",
31
+ "require": "./dist/drivers/opfs.cjs",
32
+ "default": "./dist/drivers/opfs.js"
33
+ },
34
+ "./drivers/syncStorage": {
35
+ "types": "./dist/drivers/syncStorage.d.ts",
36
+ "require": "./dist/drivers/syncStorage.cjs",
37
+ "default": "./dist/drivers/syncStorage.js"
38
+ }
39
+ },
40
+ "sideEffects": false,
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "README-JA.md",
45
+ "LICENSE"
46
+ ],
47
+ "keywords": [
48
+ "storage-engine",
49
+ "key-value",
50
+ "typescript",
51
+ "lightweight",
52
+ "browser",
53
+ "nodejs",
54
+ "database",
55
+ "indexeddb",
56
+ "opfs",
57
+ "embedded"
58
+ ],
59
+ "author": "Hajime Sano",
60
+ "license": "MIT",
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/hjmsano/frostpillar-storage-engine.git"
64
+ },
65
+ "homepage": "https://github.com/hjmsano/frostpillar-storage-engine#readme",
66
+ "bugs": {
67
+ "url": "https://github.com/hjmsano/frostpillar-storage-engine/issues"
68
+ },
69
+ "engines": {
70
+ "node": ">=24.0.0",
71
+ "pnpm": ">=10.0.0"
72
+ },
73
+ "devDependencies": {
74
+ "@eslint/js": "^10.0.1",
75
+ "@types/node": "^24.0.0",
76
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
77
+ "@typescript-eslint/parser": "^8.56.1",
78
+ "esbuild": "^0.27.3",
79
+ "eslint": "^10.0.3",
80
+ "eslint-config-prettier": "^10.1.8",
81
+ "textlint": "^15.5.2",
82
+ "textlint-rule-preset-ja-technical-writing": "^12.0.2",
83
+ "prettier": "^3.2.5",
84
+ "typescript": "^5.9.3",
85
+ "typescript-eslint": "^8.56.1"
86
+ },
87
+ "publishConfig": {
88
+ "registry": "https://registry.npmjs.org",
89
+ "access": "public"
90
+ },
91
+ "dependencies": {
92
+ "@frostpillar/frostpillar-btree": "0.2.0"
93
+ },
94
+ "scripts": {
95
+ "clean:build": "rm -rf dist tsconfig.tsbuildinfo",
96
+ "build": "pnpm clean:build && tsc --build && pnpm build:cjs",
97
+ "build:cjs": "esbuild src/index.ts src/drivers/file.ts src/drivers/localStorage.ts src/drivers/indexedDB.ts src/drivers/opfs.ts src/drivers/syncStorage.ts --bundle --packages=external --platform=node --format=cjs --target=es2022 --tsconfig=tsconfig.bundle.json --outdir=dist --outbase=src --out-extension:.js=.cjs",
98
+ "build:bundle": "tsc --project tsconfig.bundle.json --noEmit && esbuild src/index.ts --bundle --minify --target=es2020 --tsconfig=tsconfig.bundle.json --platform=browser --format=iife --global-name=FrostpillarStorageEngine --outfile=dist/frostpillar-storage-engine.min.js",
99
+ "lint": "eslint .",
100
+ "format": "prettier --write .",
101
+ "textlint": "textlint \"**/*.md\"",
102
+ "textlint:fix": "textlint \"**/*.md\" --fix",
103
+ "format:md": "pnpm textlint:fix",
104
+ "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm textlint",
105
+ "test": "node ./scripts/run-tests.mjs",
106
+ "test:coverage": "node ./scripts/run-tests.mjs --experimental-test-coverage",
107
+ "typecheck": "tsc --noEmit",
108
+ "bench": "node ./scripts/run-benchmarks.mjs"
109
+ }
110
+ }