A system for building static webapps
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: enable async migration functions

+235 -35
+1 -1
deno.json
··· 38 38 } 39 39 }, 40 40 "tasks": { 41 - "cli": "deno install packages/cli/main.ts -Afg --name=civ --config ./deno.json", 41 + "cli": "deno install packages/cli/main.ts -Afg --name=civ --config ./packages/cli/deno.json", 42 42 "test": "deno fmt && deno lint && deno test packages/ -A --coverage --quiet" 43 43 }, 44 44 "imports": {
+206 -4
deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 + "jsr:@astral/astral@~0.5.5": "0.5.5", 4 5 "jsr:@b-fuze/deno-dom@~0.1.56": "0.1.56", 6 + "jsr:@cliffy/ansi@1.0.0": "1.0.0", 7 + "jsr:@cliffy/command@1.0.0": "1.0.0", 8 + "jsr:@cliffy/flags@1.0.0": "1.0.0", 9 + "jsr:@cliffy/internal@1.0.0": "1.0.0", 10 + "jsr:@cliffy/table@1.0.0": "1.0.0", 11 + "jsr:@deno-library/progress@^1.5.1": "1.5.1", 12 + "jsr:@deno/cache-dir@0.22.2": "0.22.2", 13 + "jsr:@denosaurs/plug@1.1.0": "1.1.0", 14 + "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", 5 15 "jsr:@paulmillr/qr@~0.5.5": "0.5.5", 16 + "jsr:@rodney/parsedown@^1.4.3": "1.4.3", 6 17 "jsr:@std/assert@^1.0.17": "1.0.19", 7 18 "jsr:@std/assert@^1.0.19": "1.0.19", 19 + "jsr:@std/async@1": "1.2.0", 8 20 "jsr:@std/async@^1.1.0": "1.2.0", 21 + "jsr:@std/bytes@^1.0.2": "1.0.6", 22 + "jsr:@std/cli@^1.0.28": "1.0.28", 23 + "jsr:@std/collections@^1.1.3": "1.1.6", 9 24 "jsr:@std/data-structures@^1.0.10": "1.0.10", 25 + "jsr:@std/dotenv@~0.225.6": "0.225.6", 26 + "jsr:@std/encoding@1": "1.0.10", 27 + "jsr:@std/encoding@^1.0.10": "1.0.10", 28 + "jsr:@std/encoding@^1.0.5": "1.0.10", 29 + "jsr:@std/fmt@1": "1.0.9", 30 + "jsr:@std/fmt@1.0.3": "1.0.3", 31 + "jsr:@std/fmt@^1.0.3": "1.0.9", 32 + "jsr:@std/fmt@^1.0.9": "1.0.9", 33 + "jsr:@std/front-matter@^1.0.9": "1.0.9", 34 + "jsr:@std/fs@1": "1.0.23", 35 + "jsr:@std/fs@^1.0.22": "1.0.23", 10 36 "jsr:@std/fs@^1.0.23": "1.0.23", 37 + "jsr:@std/fs@^1.0.6": "1.0.23", 11 38 "jsr:@std/html@^1.0.5": "1.0.5", 39 + "jsr:@std/http@^1.0.25": "1.0.25", 12 40 "jsr:@std/internal@^1.0.12": "1.0.12", 41 + "jsr:@std/io@0.225": "0.225.0", 42 + "jsr:@std/io@0.225.0": "0.225.0", 43 + "jsr:@std/media-types@^1.1.0": "1.1.0", 44 + "jsr:@std/net@^1.0.6": "1.0.6", 45 + "jsr:@std/path@1": "1.1.4", 46 + "jsr:@std/path@^1.0.6": "1.1.4", 47 + "jsr:@std/path@^1.0.8": "1.1.4", 13 48 "jsr:@std/path@^1.1.4": "1.1.4", 14 49 "jsr:@std/semver@^1.0.8": "1.0.8", 50 + "jsr:@std/streams@^1.0.17": "1.0.17", 15 51 "jsr:@std/testing@^1.0.17": "1.0.17", 52 + "jsr:@std/text@^1.0.17": "1.0.17", 53 + "jsr:@std/toml@^1.0.3": "1.0.11", 54 + "jsr:@std/yaml@^1.0.5": "1.0.12", 55 + "jsr:@zip-js/zip-js@^2.7.52": "2.8.24", 16 56 "npm:@hono/zod-openapi@^1.2.2": "1.2.2_hono@4.12.5_zod@4.3.6", 17 57 "npm:@oslojs/crypto@^1.0.1": "1.0.1", 18 58 "npm:@oslojs/encoding@^1.1.0": "1.1.0", ··· 27 67 "npm:zod@^4.3.6": "4.3.6" 28 68 }, 29 69 "jsr": { 70 + "@astral/astral@0.5.5": { 71 + "integrity": "01b258a7021556f2af8526903320ea90ee5f6045771e6091529fce7822550dcb", 72 + "dependencies": [ 73 + "jsr:@deno-library/progress", 74 + "jsr:@deno/cache-dir", 75 + "jsr:@std/async@1", 76 + "jsr:@std/encoding@1", 77 + "jsr:@std/fs@1", 78 + "jsr:@std/path@1", 79 + "jsr:@zip-js/zip-js" 80 + ] 81 + }, 30 82 "@b-fuze/deno-dom@0.1.56": { 31 - "integrity": "8030e2dc1d8750f1682b53462ab893d9c3470f2287feecbe22f44a88c54ab148" 83 + "integrity": "8030e2dc1d8750f1682b53462ab893d9c3470f2287feecbe22f44a88c54ab148", 84 + "dependencies": [ 85 + "jsr:@denosaurs/plug" 86 + ] 87 + }, 88 + "@cliffy/ansi@1.0.0": { 89 + "integrity": "987008f74e50aa72cc1517ffccc769711734a14927bc4599e052efe1b9a840e2", 90 + "dependencies": [ 91 + "jsr:@cliffy/internal", 92 + "jsr:@std/encoding@^1.0.10", 93 + "jsr:@std/fmt@^1.0.9" 94 + ] 95 + }, 96 + "@cliffy/command@1.0.0": { 97 + "integrity": "c52a241ea68857fcdaff4f3173eb404f8017d7bc35553b6f533c592b89dde7d2", 98 + "dependencies": [ 99 + "jsr:@cliffy/flags", 100 + "jsr:@cliffy/internal", 101 + "jsr:@cliffy/table", 102 + "jsr:@std/fmt@^1.0.9", 103 + "jsr:@std/semver", 104 + "jsr:@std/text" 105 + ] 106 + }, 107 + "@cliffy/flags@1.0.0": { 108 + "integrity": "8b57698adc644da8f90422d58976362d41a4ebca39c312ca1c101585d0148feb", 109 + "dependencies": [ 110 + "jsr:@cliffy/internal", 111 + "jsr:@std/text" 112 + ] 113 + }, 114 + "@cliffy/internal@1.0.0": { 115 + "integrity": "1e17ccbcd5420093c0a93e5b3827bbdc9abac5195bacf187edc44665e54bdde6" 116 + }, 117 + "@cliffy/table@1.0.0": { 118 + "integrity": "3fdaa9e1ef1ea62022108adabd826932bdea8dd05497079896febcd41322907f", 119 + "dependencies": [ 120 + "jsr:@std/fmt@^1.0.9" 121 + ] 122 + }, 123 + "@deno-library/progress@1.5.1": { 124 + "integrity": "966611826b8bb27baae73ab1c4fa4317cd4edd2abb99750cd6f8488d22d5b121", 125 + "dependencies": [ 126 + "jsr:@std/fmt@1.0.3", 127 + "jsr:@std/io@0.225.0" 128 + ] 129 + }, 130 + "@deno/cache-dir@0.22.2": { 131 + "integrity": "0c84b8db6175618cc2e25ed7d7648d83b38e298c14c1aae1e4b4e1b2219b840c", 132 + "dependencies": [ 133 + "jsr:@std/fmt@^1.0.3", 134 + "jsr:@std/fs@^1.0.6", 135 + "jsr:@std/io@0.225", 136 + "jsr:@std/path@^1.0.8" 137 + ] 138 + }, 139 + "@denosaurs/plug@1.1.0": { 140 + "integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044", 141 + "dependencies": [ 142 + "jsr:@std/encoding@1", 143 + "jsr:@std/fmt@1", 144 + "jsr:@std/fs@1", 145 + "jsr:@std/path@1" 146 + ] 147 + }, 148 + "@luca/esbuild-deno-loader@0.11.1": { 149 + "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", 150 + "dependencies": [ 151 + "jsr:@std/bytes", 152 + "jsr:@std/encoding@^1.0.5", 153 + "jsr:@std/path@^1.0.6" 154 + ] 32 155 }, 33 156 "@paulmillr/qr@0.5.5": { 34 157 "integrity": "2f8ff22c8d2194f2147eac1b3093f5e85f648c0a8005d5635a617fb72bf5ae38" 35 158 }, 159 + "@rodney/parsedown@1.4.3": { 160 + "integrity": "fd5cbee4554286fc835a0157f7cb28d2c4de6ac82ed62b6b2f91291eaa9fbb2f" 161 + }, 36 162 "@std/assert@1.0.19": { 37 163 "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", 38 164 "dependencies": [ ··· 42 168 "@std/async@1.2.0": { 43 169 "integrity": "c059c6f6d95ca7cc012ae8e8d7164d1697113d54b0b679e4372b354b11c2dee5" 44 170 }, 171 + "@std/bytes@1.0.6": { 172 + "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" 173 + }, 174 + "@std/cli@1.0.28": { 175 + "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a" 176 + }, 177 + "@std/collections@1.1.6": { 178 + "integrity": "b458160ce65ea5ad35da05d0a5cbee4b583677c8b443a10d7beb0c4ac63f2baa" 179 + }, 45 180 "@std/data-structures@1.0.10": { 46 181 "integrity": "f574f86b0e07c69b9edc555fcc814b57d29258bad39fd5a34ba8a80ecf033cfe" 47 182 }, 183 + "@std/dotenv@0.225.6": { 184 + "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" 185 + }, 186 + "@std/encoding@1.0.10": { 187 + "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 188 + }, 189 + "@std/fmt@1.0.3": { 190 + "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" 191 + }, 192 + "@std/fmt@1.0.9": { 193 + "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" 194 + }, 195 + "@std/front-matter@1.0.9": { 196 + "integrity": "ee6201d06674cbef137dda2252f62477450b48249e7d8d9ab57a30f85ff6f051", 197 + "dependencies": [ 198 + "jsr:@std/toml", 199 + "jsr:@std/yaml" 200 + ] 201 + }, 48 202 "@std/fs@1.0.23": { 49 203 "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", 50 204 "dependencies": [ 51 - "jsr:@std/path" 205 + "jsr:@std/internal", 206 + "jsr:@std/path@^1.1.4" 52 207 ] 53 208 }, 54 209 "@std/html@1.0.5": { 55 210 "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" 56 211 }, 212 + "@std/http@1.0.25": { 213 + "integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193", 214 + "dependencies": [ 215 + "jsr:@std/cli", 216 + "jsr:@std/encoding@^1.0.10", 217 + "jsr:@std/fmt@^1.0.9", 218 + "jsr:@std/fs@^1.0.23", 219 + "jsr:@std/html", 220 + "jsr:@std/media-types", 221 + "jsr:@std/net", 222 + "jsr:@std/path@^1.1.4", 223 + "jsr:@std/streams" 224 + ] 225 + }, 57 226 "@std/internal@1.0.12": { 58 227 "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 59 228 }, 229 + "@std/io@0.225.0": { 230 + "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", 231 + "dependencies": [ 232 + "jsr:@std/bytes" 233 + ] 234 + }, 235 + "@std/media-types@1.1.0": { 236 + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" 237 + }, 238 + "@std/net@1.0.6": { 239 + "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" 240 + }, 60 241 "@std/path@1.1.4": { 61 242 "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", 62 243 "dependencies": [ ··· 65 246 }, 66 247 "@std/semver@1.0.8": { 67 248 "integrity": "dc830e8b8b6a380c895d53fbfd1258dc253704ca57bbe1629ac65fd7830179b7" 249 + }, 250 + "@std/streams@1.0.17": { 251 + "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" 68 252 }, 69 253 "@std/testing@1.0.17": { 70 254 "integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681", 71 255 "dependencies": [ 72 256 "jsr:@std/assert@^1.0.17", 73 - "jsr:@std/async", 74 - "jsr:@std/data-structures" 257 + "jsr:@std/async@^1.1.0", 258 + "jsr:@std/data-structures", 259 + "jsr:@std/fs@^1.0.22", 260 + "jsr:@std/internal", 261 + "jsr:@std/path@^1.1.4" 75 262 ] 263 + }, 264 + "@std/text@1.0.17": { 265 + "integrity": "4b2c4ef67ae5b6c1dfd447c81c83a43718f52e3c7e748d8b33f694aba9895f95" 266 + }, 267 + "@std/toml@1.0.11": { 268 + "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", 269 + "dependencies": [ 270 + "jsr:@std/collections" 271 + ] 272 + }, 273 + "@std/yaml@1.0.12": { 274 + "integrity": "7deabca4545bcedd07c5f69ea53acea71b8b4c67562f224e17b90d75944cb20c" 275 + }, 276 + "@zip-js/zip-js@2.8.24": { 277 + "integrity": "3e5744893e2b08f82ec3706ffad05398ee03ab2628457d805e449efda354994b" 76 278 } 77 279 }, 78 280 "npm": {
+1 -1
packages/store/deno.json
··· 1 1 { 2 2 "name": "@civility/store", 3 - "version": "0.3.0", 3 + "version": "0.3.1", 4 4 "exports": { 5 5 "./state": "./state.ts", 6 6 "./storage": "./storage.ts",
+12 -12
packages/store/storage.ts
··· 60 60 * @param data The data to migrate (type unknown as it may be from an older schema) 61 61 * @returns Migrated data (unknown since intermediate steps may have different types) 62 62 */ 63 - export type MigrationFunction = (data: unknown) => unknown 63 + export type MigrationFunction = (data: unknown) => unknown | Promise<unknown> 64 64 65 65 /** 66 66 * Single migration step in a migration chain ··· 399 399 * @returns Migrated data 400 400 * @throws StorageErrorClass if migration fails and no onError handler is provided 401 401 */ 402 - protected applyMigrations( 402 + protected async applyMigrations( 403 403 data: unknown, 404 404 dataVersion: Version | undefined, 405 - ): T { 405 + ): Promise<T> { 406 406 if (!this.migrations) return data as T 407 407 408 408 const currentVersion = this.migrations.currentVersion ··· 436 436 } else { 437 437 // Continue with normal migration chain 438 438 try { 439 - finalData = this.performMigrationChain( 439 + finalData = await this.performMigrationChain( 440 440 data, 441 441 dataVersion, 442 442 currentVersion, ··· 454 454 } else { 455 455 // No onVersionMismatch handler, proceed with migration chain 456 456 try { 457 - finalData = this.performMigrationChain( 457 + finalData = await this.performMigrationChain( 458 458 data, 459 459 dataVersion, 460 460 currentVersion, ··· 479 479 /** 480 480 * Helper method to perform the actual migration chain 481 481 */ 482 - private performMigrationChain( 482 + private async performMigrationChain( 483 483 data: unknown, 484 484 dataVersion: Version | undefined, 485 485 currentVersion: Version, 486 - ): unknown { 486 + ): Promise<unknown> { 487 487 const chain = this.buildMigrationChain(dataVersion, currentVersion, data) 488 488 489 489 let migratedData = data ··· 491 491 const { fromVersion, toVersion } = step 492 492 console.info(`Applying migration: ${fromVersion} -> ${toVersion}`) 493 493 try { 494 - migratedData = step.migrate(migratedData) 494 + migratedData = await step.migrate(migratedData) 495 495 } catch (error) { 496 496 console.error(`Migration failed: ${fromVersion} -> ${toVersion}`, error) 497 497 throw new StorageErrorClass({ ··· 525 525 } 526 526 527 527 /** Parse metadata wrapper, returning defaultValue if invalid */ 528 - parseMetadata(toParse: string): StorageMetadata<T> { 528 + async parseMetadata(toParse: string): Promise<StorageMetadata<T>> { 529 529 try { 530 530 if (!toParse) return this.wrapWithMetadata(this.defaultValue) 531 531 ··· 545 545 546 546 if (this.migrations) { 547 547 if (compare(dataVersion, this.migrations.currentVersion) !== 0) { 548 - data = this.applyMigrations(data, dataVersion) 548 + data = await this.applyMigrations(data, dataVersion) 549 549 } 550 550 } 551 551 ··· 566 566 567 567 if (this.migrations) { 568 568 if (compare(dataVersion, this.migrations.currentVersion) !== 0) { 569 - rawData = this.applyMigrations(rawData, dataVersion) 569 + rawData = await this.applyMigrations(rawData, dataVersion) 570 570 } 571 571 } 572 572 ··· 581 581 582 582 if (this.migrations) { 583 583 if (compare(dataVersion, this.migrations.currentVersion) !== 0) { 584 - rawData = this.applyMigrations(rawData, dataVersion) 584 + rawData = await this.applyMigrations(rawData, dataVersion) 585 585 } 586 586 } 587 587
+3 -3
packages/store/storage/deno_fs_storage.ts
··· 47 47 override async get(): Promise<T> { 48 48 try { 49 49 const value = await Deno.readTextFile(this.name) 50 - return this.parseMetadata(value).data 50 + return (await this.parseMetadata(value)).data 51 51 } catch (error) { 52 52 if (error instanceof Deno.errors.NotFound) { 53 53 return this.defaultValue ··· 65 65 let existingMetadata: StorageMetadata<T> | undefined 66 66 try { 67 67 const existingValue = await Deno.readTextFile(this.name) 68 - existingMetadata = this.parseMetadata(existingValue) 68 + existingMetadata = await this.parseMetadata(existingValue) 69 69 } catch (error) { 70 70 if (!(error instanceof Deno.errors.NotFound)) { 71 71 throw error ··· 106 106 override async getMetadata(): Promise<StorageMetadata<T> | null> { 107 107 try { 108 108 const value = await Deno.readTextFile(this.name) 109 - return this.parseMetadata(value) 109 + return await this.parseMetadata(value) 110 110 } catch (error) { 111 111 if (error instanceof Deno.errors.NotFound) { 112 112 return null
+3 -3
packages/store/storage/index_db_storage.ts
··· 120 120 return this.defaultValue 121 121 } 122 122 123 - const metadata = this.parseMetadata(result) 123 + const metadata = await this.parseMetadata(result) 124 124 return metadata.data 125 125 } catch (error) { 126 126 console.error('IndexDBStorage.get error:', error) ··· 141 141 142 142 const existingMetadata = 143 143 existingResult && typeof existingResult === 'string' 144 - ? this.parseMetadata(existingResult) 144 + ? await this.parseMetadata(existingResult) 145 145 : undefined 146 146 147 147 // Wrap with metadata ··· 186 186 return null 187 187 } 188 188 189 - return this.parseMetadata(result) 189 + return await this.parseMetadata(result) 190 190 } catch (error) { 191 191 console.error('IndexDBStorage.getMetadata error:', error) 192 192 return null
+9 -11
packages/store/storage/local_storage.ts
··· 34 34 } 35 35 36 36 /** Retrieve a value from storage */ 37 - override get(): Promise<T> { 37 + override async get(): Promise<T> { 38 38 const value = globalThis.localStorage.getItem(this.name) 39 - if (value == null) return Promise.resolve(this.defaultValue) 40 - return Promise.resolve(this.parseMetadata(value).data) 39 + if (value == null) return this.defaultValue 40 + return (await this.parseMetadata(value)).data 41 41 } 42 42 43 43 /** Add a value to storage */ 44 - override set(value: T): Promise<boolean> { 44 + override async set(value: T): Promise<boolean> { 45 45 if (!this.verify(value)) throw new Error('invalid value') 46 46 try { 47 47 const existingValue = globalThis.localStorage.getItem(this.name) 48 48 const existingMetadata = existingValue 49 - ? this.parseMetadata(existingValue) 49 + ? await this.parseMetadata(existingValue) 50 50 : undefined 51 51 52 52 const metadata = this.wrapWithMetadata(value, existingMetadata) ··· 55 55 this.name, 56 56 this.serializeMetadata(metadata), 57 57 ) 58 - return Promise.resolve(true) 58 + return true 59 59 } catch (err: unknown) { 60 60 if (err instanceof Error && err.name == 'QuotaExceededError') { 61 61 throw new Error('Local-storage is full.', { cause: err }) ··· 71 71 } 72 72 73 73 /** Get metadata for the stored item */ 74 - override getMetadata(): Promise<StorageMetadata<T> | null> { 74 + override async getMetadata(): Promise<StorageMetadata<T> | null> { 75 75 const value = globalThis.localStorage.getItem(this.name) 76 - if (value == null) { 77 - return Promise.resolve(null) 78 - } 79 - return Promise.resolve(this.parseMetadata(value)) 76 + if (value == null) return null 77 + return await this.parseMetadata(value) 80 78 } 81 79 }