diff --git a/.github/workflows/mam.yml b/.github/workflows/mam.yml new file mode 100644 index 00000000000..21d5be88a17 --- /dev/null +++ b/.github/workflows/mam.yml @@ -0,0 +1,29 @@ +name: mam + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - '.github/workflows/mam.yml' + - 'build/**' + pull_request: + schedule: + - cron: "0 7 * * *" + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: hyoo-ru/mam_build@master2 + with: + package: mol + modules: build + - uses: JS-DevTools/npm-publish@v1 + if: ${{ github.ref == 'refs/heads/master' }} + with: + token: ${{ secrets.NPM_AUTH_TOKEN }} + package: ./mol/build/-/package.json diff --git a/app/docs/demo.meta.tree b/app/docs/demo.meta.tree index 883262772fc..e00048ef7d6 100644 --- a/app/docs/demo.meta.tree +++ b/app/docs/demo.meta.tree @@ -88,6 +88,7 @@ include \/mol/list/demo include \/mol/list/demo/table include \/mol/list/demo/tree include \/mol/map/yandex/demo +include \/mol/mutable/demo include \/hyoo/marked/demo include \/hyoo/harp/demo - include \/mol/message/demo diff --git a/assert/assert.test.ts b/assert/assert.test.ts index 4a4e2db1598..41cef10cd2c 100644 --- a/assert/assert.test.ts +++ b/assert/assert.test.ts @@ -18,11 +18,11 @@ namespace $ { } , 'two must be unique'() { - $mol_assert_unique( [ 3 ] , [ 3 ] ) + $mol_assert_unique( [ 2 ] , [ 3 ] ) } , 'three must be unique'() { - $mol_assert_unique( [ 3 ] , [ 3 ] , [ 3 ] ) + $mol_assert_unique( [ 1 ] , [ 2 ] , [ 3 ] ) } , 'two must be alike'() { diff --git a/assert/assert.ts b/assert/assert.ts index a067bde7da9..5520e2c1cfa 100644 --- a/assert/assert.ts +++ b/assert/assert.ts @@ -2,10 +2,7 @@ namespace $ { /** * Argument must be Truthy - * @example - * $mol_assert_ok( 1 ) // Passes - * $mol_assert_ok( 0 ) // Fails because Falsy - * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf + * @deprecated use $mol_assert_equal instead */ export function $mol_assert_ok( value : any ) { if( value ) return @@ -14,10 +11,7 @@ namespace $ { /** * Argument must be Falsy - * @example - * $mol_assert_ok( 0 ) // Passes - * $mol_assert_ok( 1 ) // Fails because Truthy - * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf + * @deprecated use $mol_assert_equal instead */ export function $mol_assert_not( value : any ) { if( !value ) return @@ -32,7 +26,10 @@ namespace $ { * $mol_assert_fail( ()=>{ throw new Error( 'Parse error' ) } , Error ) // Passes because throws right class * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf */ - export function $mol_assert_fail( handler : ()=> any , ErrorRight? : any ) { + export function $mol_assert_fail( + handler: ()=> any , + ErrorRight: string | typeof Error | typeof Promise + ) { const fail = $.$mol_fail @@ -43,14 +40,12 @@ namespace $ { } catch( error: any ) { - if( !ErrorRight ) return error - $.$mol_fail = fail if( typeof ErrorRight === 'string' ) { $mol_assert_equal( error.message, ErrorRight ) } else { - $mol_assert_ok( error instanceof ErrorRight ) + $mol_assert_equal( error instanceof ErrorRight, true ) } return error @@ -62,77 +57,66 @@ namespace $ { $mol_fail( new Error( 'Not failed' ) ) } - /** - * All arguments must be equal. - * @example - * $mol_assert_equal( 1 , 1 , 1 ) // Passes - * $mol_assert_equal( 1 , 1 , 2 ) // Fails because 1 !== 2 - * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf - */ - export function $mol_assert_equal< Value >( ... args : [ Value, Value, ...Value[] ] ) { - for( let i = 0 ; i < args.length ; ++i ) { - for( let j = 0 ; j < args.length ; ++j ) { - if( i === j ) continue - if( Number.isNaN( args[i] as any as number ) && Number.isNaN( args[j] as any as number ) ) continue - if( args[i] !== args[j] ) $mol_fail( new Error( `Not equal (${i+1}:${j+1})\n${ args[i] }\n${ args[j] }` ) ) - } - } + /** @deprecated Use $mol_assert_equal */ + export function $mol_assert_like< Value >( ... args : [ Value, Value, ...Value[] ] ) { + $mol_assert_equal( ... args ) } /** - * All arguments must be not equal to each other. + * All arguments must not be structural equal to each other. * @example * $mol_assert_unique( 1 , 2 , 3 ) // Passes * $mol_assert_unique( 1 , 1 , 2 ) // Fails because 1 === 1 * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf */ export function $mol_assert_unique( ... args : [ any, any, ...any[] ] ) { + for( let i = 0 ; i < args.length ; ++i ) { for( let j = 0 ; j < args.length ; ++j ) { + if( i === j ) continue - if( args[i] === args[j] || ( Number.isNaN( args[i] as any as number ) && Number.isNaN( args[j] as any as number ) ) ) { - $mol_fail( new Error( `args[${ i }] = args[${ j }] = ${ args[i] }` ) ) - } + if( !$mol_compare_deep( args[i], args[j] ) ) continue + + $mol_fail( new Error( `args[${i}] = args[${j}] = ${ print( args[i] ) }` ) ) + } } + } /** - * All arguments must be like each other (deep structural comparison). + * All arguments must be structural equal each other. * @example * $mol_assert_like( [1] , [1] , [1] ) // Passes * $mol_assert_like( [1] , [1] , [2] ) // Fails because 1 !== 2 * @see https://mol.hyoo.ru/#!section=docs/=9q9dv3_fgxjsf */ - export function $mol_assert_like< Value >( head : Value, ... tail : Value[]) { - for( let [ index, value ] of Object.entries( tail ) ) { - - if( !$mol_compare_deep( value , head ) ) { - - const print = ( val : any ) => { - - if( !val ) return val - if( typeof val !== 'object' ) return val - if( 'outerHTML' in val ) return val.outerHTML - - try { - return JSON.stringify( val, ( k, v )=> typeof v === 'bigint' ? String(v) : v,'\t' ) - } catch( error: any ) { - console.error( error ) - return val - } - - } - - return $mol_fail( new Error( `Not like (1:${ + index + 2 })\n${ print( head ) }\n---\n${ print( value ) }` ) ) - - } - + export function $mol_assert_equal< Value >( ... args : Value[] ) { + for( let i = 1 ; i < args.length ; ++i ) { + + if( $mol_compare_deep( args[0] , args[i] ) ) continue + if( args[0] instanceof $mol_dom_context.Element && args[i] instanceof $mol_dom_context.Element && args[0].outerHTML === ( args[i] as Element ).outerHTML ) continue + + return $mol_fail( new Error( `args[0] ≠ args[${i}]\n${ print( args[0] ) }\n---\n${ print( args[i] ) }` ) ) + } } - export function $mol_assert_dom( left: Element, right: Element ) { - $mol_assert_equal( $mol_dom_serialize( left ), $mol_dom_serialize( right ) ) + const print = ( val : any ) => { + + if( !val ) return val + if( typeof val === 'bigint' ) return String(val) + 'n' + if( typeof val === 'symbol' ) return `Symbol(${val.description})` + if( typeof val !== 'object' ) return val + if( 'outerHTML' in val ) return val.outerHTML + + try { + return JSON.stringify( val, ( k, v )=> typeof v === 'bigint' ? String(v) : v,'\t' ) + } catch( error: any ) { + console.error( error ) + return val + } + } - + } diff --git a/assert/demo/demo.view.tree b/assert/demo/demo.view.tree index 0db233edbe9..377d7d6a15a 100644 --- a/assert/demo/demo.view.tree +++ b/assert/demo/demo.view.tree @@ -1,9 +1,9 @@ $mol_assert_demo $mol_example_code code? \ - \$mol_assert_unique( 1 , 2 , 3 ) - \$mol_assert_equal( 1 , 1 , 1 ) - \$mol_assert_like( [1] , [1] , [1] ) - \$mol_assert_like( { a: 1 } , { a: 1 } , { a: 1 } ) + \$mol_assert_unique( [1], [2], [3] ) + \$mol_assert_equal( [7] , [7], [7] ) + \$mol_assert_fail( ()=> { throw Error( 'test' ) }, 'test' ) + \$mol_assert_fail( ()=> { throw RangeError( 'test' ) }, RangeError ) aspects / \Algorithm/Assert \Testing diff --git a/assert/readme.md b/assert/readme.md index ecd21496682..fe0e289ec69 100644 --- a/assert/readme.md +++ b/assert/readme.md @@ -2,32 +2,25 @@ Collection of assert functions. -**`$mol_assert_unique< Value >( ... args : Value[] )`** - all arguments must be not equal to each other. +**`$mol_assert_equal( ... args )`** - all arguments must be structural equal. ```typescript -$mol_assert_unique( 1 , 2 , 3 ) // Passes -$mol_assert_unique( 1 , 1 , 2 ) // Fails because 1 === 1 +$mol_assert_equal( [1], [1], [1] ) // Passes +$mol_assert_equal( [1], [1], [2] ) // Fails because 1 !== 2 ``` -**`$mol_assert_equal< Value >( ... args : Value[] )`** - all arguments must be equal. +**`$mol_assert_unique( ... args )`** - all arguments must not be structural equal to each other. ```typescript -$mol_assert_equal( 1 , 1 , 1 ) // Passes -$mol_assert_equal( 1 , 1 , 2 ) // Fails because 1 !== 2 -``` - -**`$mol_assert_like< Value >( ... args : Value[] )`** - all arguments must be like each other (deep structural comparison). - -```typescript -$mol_assert_like( [1] , [1] , [1] ) // Passes -$mol_assert_like( [1] , [1] , [2] ) // Fails because 1 !== 2 +$mol_assert_unique( [1], [2], [3] ) // Passes +$mol_assert_unique( [1], [1], [2] ) // Fails because 1 === 1 ``` **`$mol_assert_fail( handler : ()=> any , ErrorRight? : any )`** - handler must throw an error. ```typescript -$mol_assert_fail( ()=>{ throw new Error( 'lol' ) } ) // Passes because throws error $mol_assert_fail( ()=>{ throw new Error( 'lol' ) } , 'lol' ) // Passes because throws right message +$mol_assert_fail( ()=>{ throw new Error( 'lol' ) } , 'xxx' ) // Fails because throws left message $mol_assert_fail( ()=>{ throw new Error( 'lol' ) } , Error ) // Passes because throws right class $mol_assert_fail( ()=>{ throw new Error( 'lol' ) } , RangeError ) // Fails because error isn't RangeError ``` diff --git a/audio/context/context.ts b/audio/context/context.ts new file mode 100644 index 00000000000..48a79244ba8 --- /dev/null +++ b/audio/context/context.ts @@ -0,0 +1,9 @@ +namespace $ { + export class $mol_audio_context extends $mol_object2 { + @ $mol_memo.method + static context() { + const AudioContext = this.$.$mol_dom_context.AudioContext || this.$.$node['web-audio-api'].AudioContext + return new AudioContext() + } + } +} diff --git a/audio/demo/demo.web.view.tree b/audio/demo/demo.view.tree similarity index 93% rename from audio/demo/demo.web.view.tree rename to audio/demo/demo.view.tree index 8b59d4f17ce..47f86c0f1c0 100644 --- a/audio/demo/demo.web.view.tree +++ b/audio/demo/demo.view.tree @@ -2,13 +2,13 @@ $mol_audio_demo $mol_example_small title \WebAudio API example Beep $mol_audio_room play => beep_play - duration 100 + duration 0.1 input / <= Beep_vibe $mol_audio_vibe freq 440 Noise $mol_audio_room play => noise_play - duration 1000 + duration 1 input / <= Noise_vibe $mol_audio_vibe freq <= noise_freq 440 diff --git a/audio/demo/demo.web.view.ts b/audio/demo/demo.view.ts similarity index 100% rename from audio/demo/demo.web.view.ts rename to audio/demo/demo.view.ts diff --git a/audio/demo/vibe/vibe.web.view.tree b/audio/demo/vibe/vibe.view.tree similarity index 89% rename from audio/demo/vibe/vibe.web.view.tree rename to audio/demo/vibe/vibe.view.tree index 2676dbc9c1f..2e0232df7db 100644 --- a/audio/demo/vibe/vibe.web.view.tree +++ b/audio/demo/vibe/vibe.view.tree @@ -10,11 +10,11 @@ $mol_audio_demo_vibe $mol_example_small sub / <= List $mol_list rows / <= Duration $mol_labeler - title <= duration_label \Duration, ms + title <= duration_label \Duration, s content / <= Duration_num $mol_number - precision_change 50 - value? <=> duration? 500 + precision_change 0.05 + value? <=> duration? 0.5 <= Frequency $mol_labeler title <= frequency_label \Frequency, Hz content / @@ -28,7 +28,7 @@ $mol_audio_demo_vibe $mol_example_small Filter null value? <=> shape? null options /$mol_audio_vibe_shape - \sine + \sine \square \sawtooth \triangle diff --git a/audio/demo/vibe/vibe.web.view.ts b/audio/demo/vibe/vibe.view.ts similarity index 100% rename from audio/demo/vibe/vibe.web.view.ts rename to audio/demo/vibe/vibe.view.ts diff --git a/audio/gain/gain.ts b/audio/gain/gain.ts new file mode 100644 index 00000000000..63c74ffa860 --- /dev/null +++ b/audio/gain/gain.ts @@ -0,0 +1,20 @@ +namespace $ { + + export class $mol_audio_gain extends $mol_audio_node { + + @ $mol_mem + override node_raw() { return this.context().createGain() } + + @ $mol_mem + override node() { + const node = super.node() + node.gain.setValueAtTime( this.gain(), this.time() ) + return node + } + + @ $mol_mem + gain( next = 1 ) { return next } + + } + +} diff --git a/audio/gain/gain.web.ts b/audio/gain/gain.web.ts deleted file mode 100644 index f304a31bfc0..00000000000 --- a/audio/gain/gain.web.ts +++ /dev/null @@ -1,19 +0,0 @@ -namespace $ { - - export class $mol_audio_gain extends $mol_audio_node { - - @ $mol_mem - node() { return $mol_audio_node.context().createGain() } - - @ $mol_mem - gain( next = 1 ) { return next } - - @ $mol_mem - output() { - this.node().gain.setValueAtTime( this.gain(), this.time() ) - return super.output() - } - - } - -} diff --git a/audio/instrument/instrument.ts b/audio/instrument/instrument.ts new file mode 100644 index 00000000000..621fd7c6160 --- /dev/null +++ b/audio/instrument/instrument.ts @@ -0,0 +1,59 @@ +namespace $ { + export class $mol_audio_instrument extends $mol_audio_node { + override node_raw(): AudioScheduledSourceNode { + throw new Error('implement') + } + + @ $mol_mem + override node() { + const node = super.node() + node.onended = $mol_wire_async((e: Event) => this.end(e)) + + return node + } + + protected promise = $mol_promise() + + @ $mol_mem + wait() { + return this.promise + } + + end(e: Event) { + this.active( false ) + } + + @ $mol_mem + active( next?: boolean ): boolean { + + $mol_wire_solid() + + const node = next === false ? this.node_raw() : this.node() + + const prev = $mol_wire_probe( ()=> this.active() ) + if( prev === next ) return next ?? false + + if( next === true ) { + node.start() + } else if( prev === true ) { + node.stop() + this.promise.done() + this.promise = $mol_promise() + } + + return next ?? false + } + + override destructor() { + this.active( false ) + super.destructor() + } + + @ $mol_mem + override output() { + this.active( true ) + return super.output() + } + + } +} diff --git a/audio/node/node.web.ts b/audio/node/node.ts similarity index 58% rename from audio/node/node.web.ts rename to audio/node/node.ts index 71953d99772..9ad6e3de567 100644 --- a/audio/node/node.web.ts +++ b/audio/node/node.ts @@ -1,21 +1,30 @@ namespace $ { export class $mol_audio_node extends $mol_object2 { + context() { return this.$.$mol_audio_context.context() } - @ $mol_memo.method - static context() { - return new AudioContext + @ $mol_mem + node_raw() { return this.context().destination as AudioNode } + + node() { + return this.node_raw() as ReturnType } - + @ $mol_mem - node() { return $mol_audio_node.context().destination as AudioNode } + duration() { + let duration = 0 + for (const input of this.input_connected()) duration = Math.max(duration, input.duration()) + + return duration + } + @ $mol_mem input( next = [] as readonly $mol_audio_node[] ) { return next } @ $mol_mem input_connected() { - const node = this.node() + const node = this.node_raw() const prev = $mol_mem_cached( ()=> this.input_connected() ) ?? [] const next = this.input() @@ -35,14 +44,14 @@ namespace $ { @ $mol_mem output() { this.input_connected() - return this.node() + return this.node_raw() } - time() { return $mol_audio_node.context().currentTime } + time() { return this.context().currentTime } destructor() { - const node = this.node() + const node = this.node_raw() for( const src of this.input() ) { src.output().disconnect( node ) diff --git a/audio/room/room.web.ts b/audio/room/room.ts similarity index 70% rename from audio/room/room.web.ts rename to audio/room/room.ts index 6f958854117..4684f6ece70 100644 --- a/audio/room/room.web.ts +++ b/audio/room/room.ts @@ -5,14 +5,10 @@ namespace $ { */ export class $mol_audio_room extends $mol_audio_node { - duration() { - return 1000 - } - @ $mol_action play() { this.output() - this.$.$mol_wait_timeout( this.duration() ) + this.$.$mol_wait_timeout( this.duration() * 1000 ) } } diff --git a/audio/sample/sample.ts b/audio/sample/sample.ts new file mode 100644 index 00000000000..738d12d686b --- /dev/null +++ b/audio/sample/sample.ts @@ -0,0 +1,28 @@ +namespace $ { + export class $mol_audio_sample extends $mol_audio_instrument { + @ $mol_mem + override node_raw() { return this.context().createBufferSource() } + + override duration() { + return this.audio_buffer().duration + } + + buffer() { + return new ArrayBuffer(0) + } + + @ $mol_mem + audio_buffer() { + return $mol_wire_sync(this.context()).decodeAudioData(this.buffer()) + } + + @ $mol_mem + override node() { + const node = super.node() + node.buffer = this.audio_buffer() + + return node + } + + } +} diff --git a/audio/vibe/vibe.ts b/audio/vibe/vibe.ts new file mode 100644 index 00000000000..e7ac418568f --- /dev/null +++ b/audio/vibe/vibe.ts @@ -0,0 +1,38 @@ +namespace $ { + + export type $mol_audio_vibe_shape = + | 'sine' + | 'square' + | 'sawtooth' + | 'triangle' + | 'custom' + + /** + * @see https://mol.hyoo.ru/#!section=demos/demo=mol_audio_demo_vibe + */ + export class $mol_audio_vibe extends $mol_audio_instrument { + + @ $mol_mem + override node_raw() { return this.context().createOscillator() } + + @ $mol_mem + freq( next = 440 ) { return next } + + @ $mol_mem + shape( next: $mol_audio_vibe_shape = 'sine' ) { return next } + + override duration() { + return 0.5 + } + + @ $mol_mem + override node() { + const node = super.node() + node.frequency.setValueAtTime( this.freq(), this.time() ) + node.type = this.shape() + + return node + } + + } +} diff --git a/audio/vibe/vibe.web.ts b/audio/vibe/vibe.web.ts deleted file mode 100644 index 10c561a4112..00000000000 --- a/audio/vibe/vibe.web.ts +++ /dev/null @@ -1,55 +0,0 @@ -namespace $ { - - export type $mol_audio_vibe_shape = - | 'sine' - | 'square' - | 'sawtooth' - | 'triangle' - | 'custom' - - /** - * @see https://mol.hyoo.ru/#!section=demos/demo=mol_audio_demo_vibe - */ - export class $mol_audio_vibe extends $mol_audio_node { - - @ $mol_mem - node() { return $mol_audio_node.context().createOscillator() } - - @ $mol_mem - freq( next = 440 ) { return next } - - @ $mol_mem - shape( next: $mol_audio_vibe_shape = 'sine' ) { return next } - - @ $mol_mem - active( next?: boolean ): boolean { - - $mol_wire_solid() - - const prev = $mol_wire_probe( ()=> this.active() ) - if( prev === next ) return next ?? false - - if( next === true ) this.node().start() - else if( prev === true ) this.node().stop() - - return next ?? false - } - - @ $mol_mem - output() { - const node = this.node() - - node.frequency.setValueAtTime( this.freq(), this.time() ) - node.type = this.shape() - - this.active( true ) - return super.output() - } - - destructor() { - this.active( false ) - super.destructor() - } - - } -} diff --git a/book2/catalog/catalog.view.tree b/book2/catalog/catalog.view.tree index 57ee0946edc..4a7721126d3 100644 --- a/book2/catalog/catalog.view.tree +++ b/book2/catalog/catalog.view.tree @@ -7,12 +7,16 @@ $mol_book2_catalog $mol_book2 spread_ids /string menu_filter_enabled false spread_ids_filtered /string + menu_tools / + addon_tools / pages / <= Menu $mol_page Title => Menu_title title <= menu_title \ Tools => Menu_tools - tools <= menu_tools / + tools / + ^ menu_tools + ^ addon_tools head <= menu_head / <= Menu_title <= Menu_tools diff --git a/buffer/buffer.ts b/buffer/buffer.ts index c10433e1bc3..440c197ef1e 100644 --- a/buffer/buffer.ts +++ b/buffer/buffer.ts @@ -1,6 +1,11 @@ namespace $ { export class $mol_buffer extends DataView { + static from< This extends typeof $mol_buffer >( this: This, array: string | Uint8Array ) { + if( typeof array === 'string' ) array = $mol_base64_ae_decode( array ) + return new this( array.buffer, array.byteOffset, array.byteLength ) as InstanceType< This > + } + static toString() { return $$.$mol_func_name( this ) } @@ -91,5 +96,9 @@ namespace $ { return new Uint8Array( this.buffer, this.byteOffset, this.byteLength ) } + toString() { + return $mol_base64_ae_encode( this.asArray() ) + } + } } diff --git a/build/bin b/build/bin new file mode 100644 index 00000000000..44bfe930ad0 --- /dev/null +++ b/build/bin @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require( 'mam' ) diff --git a/build/build.meta.tree b/build/build.meta.tree new file mode 100644 index 00000000000..f2c6e3e1dd4 --- /dev/null +++ b/build/build.meta.tree @@ -0,0 +1 @@ +deploy \/mol/build/bin diff --git a/build/build.node.ts b/build/build.node.ts index 26ae5778c19..ddb7dfd53c4 100644 --- a/build/build.node.ts +++ b/build/build.node.ts @@ -595,17 +595,15 @@ namespace $ { if( mod.type() !== 'dir' ) return false const git_dir = mod.resolve( '.git' ) - // if( git_dir.exists() ) return false - if( git_dir.exists() ) { - this.$.$mol_exec( mod.path() , 'git' , 'pull' ) - mod.reset() - for ( const sub of mod.sub() ) { - sub.reset() - } + + this.$.$mol_exec( mod.path() , 'git' , 'pull', '--deepen=1' ) + // mod.reset() + // for ( const sub of mod.sub() ) sub.reset() + return false } - + for( let repo of mapping.select( 'pack' , mod.name() , 'git' ).sub ) { this.$.$mol_exec( mod.path() , 'git' , 'init' ) @@ -617,7 +615,7 @@ namespace $ { : matched[1] this.$.$mol_exec( mod.path() , 'git' , 'remote' , 'add' , '--track' , head_branch_name! , 'origin' , repo.value ) - this.$.$mol_exec( mod.path() , 'git' , 'pull' ) + this.$.$mol_exec( mod.path() , 'git' , 'pull', '--deepen=1' ) mod.reset() for ( const sub of mod.sub() ) { sub.reset() @@ -639,7 +637,7 @@ namespace $ { } for( let repo of mapping.select( 'pack' , mod.name() , 'git' ).sub ) { - this.$.$mol_exec( this.root().path() , 'git' , 'clone' , repo.value , mod.relate( this.root() ) ) + this.$.$mol_exec( this.root().path() , 'git' , 'clone' , '--depth', '1', repo.value , mod.relate( this.root() ) ) mod.reset() return true } @@ -1011,7 +1009,7 @@ namespace $ { $mol_fail_hidden( new $mol_error_mix( `Build fail ${path}`, ... errors ) ) } - target.text( 'console.info("Audit passed")' ) + target.text( `console.info( '%c ▫ $mol_build ▫ Audit passed', 'color:forestgreen; font-weight:bolder' )` ) return [ target ] } @@ -1286,8 +1284,6 @@ namespace $ { json.version = version.join( '.' ) - json.dependencies = {} - for( let dep of this.nodeDeps({ path , exclude }) ) { if( require('module').builtinModules.includes(dep) ) continue json.dependencies[ dep ] = `*` diff --git a/build/package.json b/build/package.json index 9e154de96e3..13a292dc9da 100644 --- a/build/package.json +++ b/build/package.json @@ -1,27 +1,16 @@ { - "name": "mam", - "version": "1.11.0", - "exports": { - "node": { - "import": "./node.mjs", - "default": "./node.js" + "name": "mam", + "version": "1.11.0", + "bin": { + "mam": "./mol/build/bin" }, - "types": "./web.d.ts", - "import": "./web.mjs", - "default": "./web.js" - }, - "main": "./node.js", - "module": "./node.mjs", - "browser": "./web.js", - "types": "./web.d.ts", - "bin": { - "mam": "./bin/mam" - }, - "dependencies": { - "@types/compression": "*", - "@types/express": "*", - "@types/node": "*", - "@types/serve-index": "*", - "@types/ws": "*" - } + "dependencies": { + "@types/node": "*", + "@types/express": "*", + "@types/compression": "*", + "@types/cors": "*", + "@types/body-parser": "*", + "@types/serve-index": "*", + "@types/ws": "*" + } } diff --git a/bus/bus.test.ts b/bus/bus.test.ts new file mode 100644 index 00000000000..2cd8ef666da --- /dev/null +++ b/bus/bus.test.ts @@ -0,0 +1,8 @@ +namespace $ { + $mol_test_mocks.push( $=> { + class $mol_bus< Data > extends $.$mol_bus< Data > { + send() {} + } + $.$mol_bus = $mol_bus + } ) +} diff --git a/bus/bus.ts b/bus/bus.ts new file mode 100644 index 00000000000..83d7c020202 --- /dev/null +++ b/bus/bus.ts @@ -0,0 +1,25 @@ +namespace $ { + export class $mol_bus< Data > extends $mol_object { + + readonly channel: BroadcastChannel + + constructor( + readonly name: string, + readonly handle: ( data: Data )=> void + ) { + super() + const channel = new BroadcastChannel( name ) + channel.onmessage = ( event: MessageEvent< Data > )=> this.handle( event.data ) + this.channel = channel + } + + destructor() { + this.channel.close() + } + + send( data: Data ) { + this.channel.postMessage( data ) + } + + } +} diff --git a/button/button.test.ts b/button/button.test.ts index 18e2210b7c9..a2a5fd5814e 100644 --- a/button/button.test.ts +++ b/button/button.test.ts @@ -37,7 +37,7 @@ namespace $.$$ { $mol_assert_not( clicked ) } , - 'Store error'($) { + async 'Store error'($) { const clicker = $mol_button.make({ $, @@ -46,6 +46,7 @@ namespace $.$$ { const event = $mol_dom_context.document.createEvent( 'mouseevent' ) $mol_assert_fail( ()=> clicker.event_activate( event ), 'Test error' ) + await Promise.resolve() $mol_assert_equal( clicker.status()[0].message, 'Test error' ) } , diff --git a/button/button.view.ts b/button/button.view.ts index 6cdbdc12644..2027e573a0d 100644 --- a/button/button.view.ts +++ b/button/button.view.ts @@ -26,7 +26,7 @@ namespace $.$$ { } catch( error: any ) { - this.status([ error ]) + Promise.resolve().then( ()=> this.status([ error ]) ) $mol_fail_hidden( error ) } diff --git a/chance/chance.test.ts b/chance/chance.test.ts index c66c2842638..7aafa072d92 100644 --- a/chance/chance.test.ts +++ b/chance/chance.test.ts @@ -4,28 +4,26 @@ namespace $ { 'Probability should be a number'() { - $mol_assert_fail( ()=> { - - $mol_chance( + $mol_assert_fail( + ()=> $mol_chance( [ 50, ()=> 1 ], [ NaN, ()=> 2 ], - ) - - } ) + ), + 'Incorrect probability value: NaN, but only positive numbers are allowed', + ) }, 'Probability should not be negative'() { - $mol_assert_fail( ()=> { - - $mol_chance( + $mol_assert_fail( + ()=> $mol_chance( [ 50, ()=> 1 ], [ -10, ()=> 2 ], [ 50, ()=> 3 ], - ) - - } ) + ), + 'Incorrect probability value: -10, but only positive numbers are allowed' + ) }, @@ -39,7 +37,7 @@ namespace $ { [ 10, ()=> 5 ], ) - $mol_assert_ok( + $mol_assert_equal( true, result === 1 || result === 2 || result === 3 @@ -55,7 +53,7 @@ namespace $ { [ 1, ()=> ( { prop: false } ) ] ) - $mol_assert_like( result, { prop: false } ) + $mol_assert_equal( result, { prop: false } ) }, diff --git a/compare/deep/deep.ts b/compare/deep/deep.ts index 9eb0c01ef4d..0d71ecdf8ac 100644 --- a/compare/deep/deep.ts +++ b/compare/deep/deep.ts @@ -76,14 +76,14 @@ namespace $ { function compare_buffer( left: ArrayBufferView, right: ArrayBufferView ): boolean { + const len = left.byteLength + if( len !== right.byteLength ) return false + if( left instanceof DataView ) return compare_buffer( new Uint8Array( left.buffer, left.byteOffset, left.byteLength ), new Uint8Array( right.buffer, left.byteOffset, left.byteLength ), ) - const len = left.byteLength - if( len !== right.byteLength ) return false - for( let i = 0; i < len; ++i ) { if( (left as any)[i] !== (right as any)[i] ) return false } diff --git a/crypto/README.md b/crypto/README.md index 22a9e7104bc..2e24258dbd4 100644 --- a/crypto/README.md +++ b/crypto/README.md @@ -2,29 +2,27 @@ Simple API for effective cross platform cryptography with minimal extra size. -## Symmetric encoding +## (a)Symmetric Encryption, Signing, Hashing -```typescript -// Any DataView or ArrayBuffer -const data = new Uint8Array([1,2,3]) - -// Should be unique for every encryption (but may be predictable) -const salt = $mol_crypto_salt() // 4 bytes -// Generates secret key -const Alice = await $mol_crypto_secret.generate() +```typescript +const Alice = await $mol_crypto_key_private.generate() // 96 B +const Bella = await $mol_crypto_key_private.generate() // 96 B -// Serialize secret key to ArrayBuffer (16 byte) -const key = await Alice.serial() +const secret = await $mol_crypto_secret.derive( + Alice.toString(), // 96 -> 129 B + Bella.public().toString() // 64 -> 86 B +) -// Reuse secret key from ArrayBuffer -const Bob = await $mol_crypto_secret.from( key ) +const data = new Uint8Array([ 1, 2, 3 ]) // 3 B +const salt = $mol_crypto_salt() // 12 B -// Use secret key and salt to encrypt data (4 bytes + data length ) -const closed = await Alice.encrypt( data, salt ) +const closed = await secret.encrypt( data, salt ) // 3+4 B +const digest = $mol_crypto_hash( closed ) // 20 B +const sign = await Alice.sign( digest ) // 64 B -// Use secret key and salt to decrypt data -const opened = await Bob.decrypt( closed, salt ) +const verified = await Alice.public().verify( digest, sign ) +const opened = await secret.decrypt( closed, salt ) // 3 B ``` ## Authentication @@ -34,58 +32,6 @@ const opened = await Bob.decrypt( closed, salt ) const Alice = await $mol_crypto_secret_id() ``` -## Asymmetric encoding - -```typescript -// Any DataView or ArrayBuffer -const data = new Uint8Array([1,2,3]) - -// Generates private-public key pair -const pair = await $mol_crypto_cipher_pair() - -// Serialize public key to ArrayBuffer (162 bytes) -const key_public = await pair.public.serial() - -// Serialize private key to ArrayBuffer (~640 bytes) -const key_private = await pair.private.serial() - -// Reuse keys from ArrayBuffer -const Alice = await $mol_crypto_cipher_public.from( key_public ) -const Bob = await $mol_crypto_cipher_private.from( key_private ) - -// Use public key to encrypt data (max 86 bytes input, 128 bytes output) -const closed = await Alice.encrypt( data ) - -// Use private key to decrypt data -const opened = await Bob.decrypt( closed ) -``` - -## Asymmetric signing - -```typescript -// Any DataView or ArrayBuffer -const data = new Uint8Array([1,2,3]) - -// Generates private-public key pair -const pair = await $mol_crypto_auditor_pair() - -// Serialize public key to ArrayBuffer (62 bytes) -const key_public = await pair.public.serial() - -// Serialize private key to ArrayBuffer (~195 bytes) -const key_private = await pair.private.serial() - -// Reuse keys from ArrayBuffer -const Alice = await $mol_crypto_auditor_public.from( key_public ) -const Bob = await $mol_crypto_auditor_private.from( key_private ) - -// Make sign for data (32 bytes) -const sign = await Alice.sign( data ) - -// Use sign to verify data -const = await Bob.verify( data, sign ) -``` - # Usage from NPM ``` diff --git a/crypto/lib/lib.meta.tree b/crypto/lib/lib.meta.tree index 68d4c872357..fe93f1417f7 100644 --- a/crypto/lib/lib.meta.tree +++ b/crypto/lib/lib.meta.tree @@ -1,5 +1,4 @@ -include \/mol/crypto/secret -include \/mol/crypto/cipher -include \/mol/crypto/auditor +include \/mol/crypto/secret/id +include \/mol/crypto/key include \/mol/crypto/hash include \/mol/crypto/salt diff --git a/crypto/lib/package.json b/crypto/lib/package.json new file mode 100644 index 00000000000..b2bf59e1291 --- /dev/null +++ b/crypto/lib/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.1" +} diff --git a/crypto/salt/salt.ts b/crypto/salt/salt.ts index 0ae41359751..ee3c3e1dd38 100644 --- a/crypto/salt/salt.ts +++ b/crypto/salt/salt.ts @@ -1,9 +1,9 @@ namespace $ { - /** 8 byte */ + /** 16 byte */ export function $mol_crypto_salt() { return $mol_crypto_native.getRandomValues( - new Uint8Array( 12 ) + new Uint8Array( 16 ) ) } diff --git a/crypto/secret/secret.test.ts b/crypto/secret/secret.test.ts index 5c8ae4c433d..ed1181fd54f 100644 --- a/crypto/secret/secret.test.ts +++ b/crypto/secret/secret.test.ts @@ -12,7 +12,7 @@ namespace $ { const salt = $mol_crypto_salt() const closed = await cipher.encrypt( data, salt ) - $mol_assert_equal( closed.byteLength, data.byteLength + $mol_crypto_secret.extra ) + $mol_assert_equal( closed.byteLength, 16 ) }, diff --git a/crypto/secret/secret.ts b/crypto/secret/secret.ts index 3cfefcf2abc..99f7070c763 100644 --- a/crypto/secret/secret.ts +++ b/crypto/secret/secret.ts @@ -1,7 +1,7 @@ namespace $ { const algorithm = { - name: 'AES-GCM', + name: 'AES-CBC', length: 128, tagLength: 32, } @@ -12,9 +12,6 @@ namespace $ { /** Key size in bytes. */ static size = 16 - /** Extra size in bytes to encrypted data. */ - static extra = 4 - constructor( readonly native: CryptoKey & { type: 'secret' } ) { @@ -104,7 +101,7 @@ namespace $ { ) } - /** 4 bytes + data length */ + /** 16n bytes */ async encrypt( open: BufferSource, salt: BufferSource ): Promise< ArrayBuffer > { return await $mol_crypto_native.subtle.encrypt( { diff --git a/data/pipe/pipe.ts b/data/pipe/pipe.ts index 05bd35d1d60..56567d1763b 100644 --- a/data/pipe/pipe.ts +++ b/data/pipe/pipe.ts @@ -38,7 +38,7 @@ namespace $ { return $mol_data_setup( function( this: any, input : $mol_type_param< Funcs[0] , 0 > ) { let value : any = input - for( const func of funcs as any ) value = $mol_func_is_class( func ) ? new func( value ) : func.call( this, value ) + for( const func of funcs ) value = $mol_func_is_class( func ) ? new func( value ) : ( func as any ).call( this, value ) return value as $mol_type_result< $mol_type_foot< Funcs > > diff --git a/db/README.md b/db/README.md index e5e37167d4d..67810a7dc7f 100644 --- a/db/README.md +++ b/db/README.md @@ -2,6 +2,43 @@ Static typed facade for [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) with simple API. +## Teaser + +```typescript +type User = { + name: string + admin: boolean +} + +// Static typed schema +type DB = { + User: { + Key: [ string ] + Doc: User + Indexes: { + Name: [ string ] + } + } +} + +// Automatic migrations +const db = await $mol_db< DB >( '$my_app', + mig => mig.store_make( 'User' ), + mig => { + const { User } = mig.stores + User.index_make( 'Name', [ 'name' ] ) + }, +) + +// Writing transaction +const { User } = db.change( 'User' ).stores +await User.put({ name: 'Jin', admin: true }) + +// Reading transaction +const { Name } = db.read( 'User' ).User.indexes +const jins = await Name.select([ 'Jin' ]) +``` + ## IndexedDB Structure - **Database** contains named Stores diff --git a/db/database/database.ts b/db/database/database.ts index 8a3a77c422e..edb98737898 100644 --- a/db/database/database.ts +++ b/db/database/database.ts @@ -25,14 +25,14 @@ namespace $ { /** Create read-only transaction. */ read< Names extends Exclude< keyof Schema, symbol | number > >( ... names: Names[] ) { return new $mol_db_transaction< Pick< Schema, Names > >( - this.native.transaction( names, 'readonly' ) + this.native.transaction( names, 'readonly', { durability: 'relaxed' } ) ).stores } /** Create read/write transaction. */ change< Names extends Exclude< keyof Schema, symbol | number > >( ... names: Names[] ) { return new $mol_db_transaction< Pick< Schema, Names > >( - this.native.transaction( names, 'readwrite' ) + this.native.transaction( names, 'readwrite', { durability: 'relaxed' } ) ) } diff --git a/db/transaction/transaction.ts b/db/transaction/transaction.ts index f9211461b28..348bdb07978 100644 --- a/db/transaction/transaction.ts +++ b/db/transaction/transaction.ts @@ -20,7 +20,9 @@ namespace $ { { ownKeys: ()=> [ ... this.native.objectStoreNames ], has: ( _, name: string )=> this.native.objectStoreNames.contains( name ), - get: ( _, name: string )=> new $mol_db_store( this.native.objectStore( name ) ), + get: ( _, name: string, proxy )=> ( name in proxy ) + ? new $mol_db_store( this.native.objectStore( name ) ) + : undefined, }, ) } diff --git a/dev/format/format.ts b/dev/format/format.ts index 07c15632518..fc4b963228f 100644 --- a/dev/format/format.ts +++ b/dev/format/format.ts @@ -50,7 +50,8 @@ namespace $ { export function $mol_dev_format_native( obj : any ) { if( typeof obj === 'undefined' ) return $mol_dev_format_shade( 'undefined' ) - if( typeof obj !== 'object' && typeof obj !== 'function' ) return obj + + // if( ![ 'object', 'function', 'symbol' ].includes( typeof obj ) ) return obj return [ 'object' , @@ -66,10 +67,6 @@ namespace $ { if( obj == null ) return $mol_dev_format_shade( String( obj ) ) - if( typeof obj === 'object' && $mol_dev_format_head in obj ) { - return obj[ $mol_dev_format_head ]() - } - return [ 'object' , { diff --git a/dom/capture/capture.tsx b/dom/capture/capture.tsx index 8262d20ce1e..e15a5adc346 100644 --- a/dom/capture/capture.tsx +++ b/dom/capture/capture.tsx @@ -1,17 +1,7 @@ /** @jsx $mol_jsx */ namespace $ { - export async function $mol_dom_capture_image( el: Element ) { - - function wait_load( el: { - onload: null | ( ( value: any )=> any ), - onerror: null | ( ( error: Event )=> any ), - } ) { - return new Promise< typeof el >( ( done, fail )=> { - el.onload = ()=> done( el ) - el.onerror = fail - } ) - } + export async function $mol_dom_capture_svg( el: Element ) { function restyle( el: HTMLElement, styles: CSSStyleDeclaration ) { for( let i= 0; i < styles.length; ++i ) { @@ -23,6 +13,7 @@ namespace $ { function clone( el: Element ) { const re = el.cloneNode() as HTMLElement + if( el instanceof HTMLImageElement && !/^(data|blob):/.test( el.src ) ) { const canvas = as HTMLCanvasElement @@ -75,9 +66,7 @@ namespace $ { const { width, height } = el.getBoundingClientRect() - // const styles = [ ... document.querySelectorAll( 'style' ) ].map( s => s.cloneNode( true ) ) - - const svg = - {/* */} - {/* { styles } */} + > { clone( el ) } - const xml = $mol_dom_serialize( svg ) + } + + export async function $mol_dom_capture_image( el: Element ) { + + const xml = $mol_dom_serialize( await $mol_dom_capture_svg( el ) ) const uri = 'data:image/svg+xml,' + encodeURIComponent( xml ) const image = as HTMLImageElement @@ -115,4 +106,14 @@ namespace $ { return canvas } + function wait_load( el: { + onload: null | ( ( value: any )=> any ), + onerror: null | ( ( error: Event )=> any ), + } ) { + return new Promise< typeof el >( ( done, fail )=> { + el.onload = ()=> done( el ) + el.onerror = fail + } ) + } + } diff --git a/dom/capture/demo/demo.ts b/dom/capture/demo/demo.ts index ce1453fa851..158b5c020a8 100644 --- a/dom/capture/demo/demo.ts +++ b/dom/capture/demo/demo.ts @@ -1,7 +1,8 @@ namespace $ { - void ( async()=> { + setTimeout( async()=> { $mol_dev_dump_canvas( await $mol_dom_capture_canvas( document.getElementById( 'example' )! ) ) - } )() + console.log( await $mol_dom_capture_svg( document.getElementById( 'example' )! ) ) + } ) } diff --git a/dom/capture/demo/index.html b/dom/capture/demo/index.html index 2a957fd0250..8d6dc831a86 100644 --- a/dom/capture/demo/index.html +++ b/dom/capture/demo/index.html @@ -25,5 +25,9 @@ F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH hhx4dbgYKAAA7" /> Example

See console output

+ + + + diff --git a/drag/demo/demo.view.css.ts b/drag/demo/demo.view.css.ts index 9481539cfc8..c78b647d7c9 100644 --- a/drag/demo/demo.view.css.ts +++ b/drag/demo/demo.view.css.ts @@ -8,7 +8,7 @@ namespace $.$$ { '@': { mol_drop_status: { drag: { - boxShadow: `0 -1px 0 0px ${ $mol_theme.focus }`, + boxShadow: `inset 0 1px 0 0px ${ $mol_theme.focus }`, }, }, }, @@ -18,21 +18,23 @@ namespace $.$$ { '@': { mol_drop_status: { drag: { - '>' : { - $mol_view : { + // '>' : { + // $mol_view : { ':last-child': { - boxShadow: `0 1px 0 0px ${ $mol_theme.focus }`, + boxShadow: `inset 0 -1px 0 0px ${ $mol_theme.focus }`, }, - }, - }, + // }, + // }, }, }, }, }, Trash: { - padding: $mol_gap.block, - display: 'block', + padding: $mol_gap.text, + flex: { + grow: 1, + }, }, Trash_drop: { @@ -47,6 +49,10 @@ namespace $.$$ { }, }, + List: { + padding: $mol_gap.text, + }, + }) } diff --git a/drag/demo/demo.view.tree b/drag/demo/demo.view.tree index e54d8106a99..072b94dd44e 100644 --- a/drag/demo/demo.view.tree +++ b/drag/demo/demo.view.tree @@ -4,14 +4,14 @@ $mol_drag_demo $mol_example_large <= List_drop $mol_drop adopt?transfer <=> transfer_adopt?transfer null receive?obj <=> receive?obj null - Sub <= Scroll $mol_scroll sub / - <= Trash_drop $mol_drop + Sub <= Page $mol_page + head / <= Trash_drop $mol_drop adopt?transfer <=> transfer_adopt?transfer null receive?obj <=> receive_trash?obj null - Sub <= Trash $mol_float sub / + Sub <= Trash $mol_view sub / <= Trash_icon $mol_icon_trash_can_outline \ Trash - <= List $mol_list + Body_content <= List $mol_list rows <= task_rows / Task_row* $mol_drag transfer * diff --git a/embed/youtube/youtube.view.ts b/embed/youtube/youtube.view.ts index 982e3baf72e..c73df39824f 100644 --- a/embed/youtube/youtube.view.ts +++ b/embed/youtube/youtube.view.ts @@ -8,7 +8,7 @@ namespace $.$$ { @ $mol_mem video_id() { - return this.uri().match( /^https\:\/\/www\.youtube\.com\/(?:embed\/|watch\?v=)([^\/&?#]+)/ )?.[1] + return this.uri().match( /^https\:\/\/www\.youtube\.com\/(?:embed\/|shorts\/|watch\?v=)([^\/&?#]+)/ )?.[1] ?? this.uri().match( /^https\:\/\/youtu\.be\/([^\/&?#]+)/ )?.[1] ?? 'about:blank' } diff --git a/func/is/class/class.ts b/func/is/class/class.ts index 3da1e283502..3903e24d839 100644 --- a/func/is/class/class.ts +++ b/func/is/class/class.ts @@ -1,6 +1,10 @@ namespace $ { - export function $mol_func_is_class( func: Function ) { + export function $mol_func_is_class< + Func extends Function + >( + func: Func + ): func is Func & ( new( ...args: any[] )=> any ) { return Object.getOwnPropertyDescriptor( func, 'prototype' )?.writable === false } diff --git a/jack/jack.test.ts b/jack/jack.test.ts index 2f5a29090e2..c9e7aba0bf8 100644 --- a/jack/jack.test.ts +++ b/jack/jack.test.ts @@ -28,14 +28,14 @@ namespace $ { ) - $mol_assert_fail( ()=> { - $.$mol_tree2_from_string( ` + $mol_assert_fail( + ()=> $.$mol_tree2_from_string( ` test case \\foo case \\bar - ` ) - .hack( root ) - } ) + ` ).hack( root ), + 'args[0] ≠ args[1]\n\\foo\n\n---\n\\bar\n\ntest\n?#2:6/4' + ) } , diff --git a/key/key.test.tsx b/key/key.test.tsx index 2331f8f6cf5..f2ae8529e87 100644 --- a/key/key.test.tsx +++ b/key/key.test.tsx @@ -21,6 +21,14 @@ namespace $ { }, + 'Uint8Array'() { + + $mol_assert_equal( $mol_key( new Uint8Array([ 1, 2 ]) ), '[1,2]' ) + $mol_assert_equal( $mol_key([ new Uint8Array([ 1, 2 ]) ]), '[[1,2]]' ) + $mol_assert_equal( $mol_key({ foo: new Uint8Array([ 1, 2 ]) }), '{"foo":[1,2]}' ) + + }, + 'Function'() { const func = ()=> {} diff --git a/key/key.ts b/key/key.ts index aa3ca545553..92d1e4e658c 100644 --- a/key/key.ts +++ b/key/key.ts @@ -6,12 +6,14 @@ namespace $ { export function $mol_key< Value >( value : Value ) : string { if( typeof value === 'bigint' ) return value.toString() + 'n' + if( typeof value === 'symbol' ) return value.description! if( !value ) return JSON.stringify( value ) if( typeof value !== 'object' && typeof value !== 'function' ) return JSON.stringify( value ) return JSON.stringify( value, ( field, value )=> { if( typeof value === 'bigint' ) return value.toString() + 'n' + if( typeof value === 'symbol' ) return value.description if( !value ) return value if( typeof value !== 'object' && typeof value !== 'function' ) return value if( Array.isArray( value ) ) return value @@ -22,6 +24,7 @@ namespace $ { if( 'toJSON' in value ) return value if( value instanceof RegExp ) return value.toString() + if( value instanceof Uint8Array ) return [ ... value ] let key = $mol_key_store.get( value ) if( key ) return key diff --git a/link/link.view.tree b/link/link.view.tree index de8008826fb..24c4c8855c8 100644 --- a/link/link.view.tree +++ b/link/link.view.tree @@ -11,6 +11,7 @@ $mol_link $mol_view target <= target \_self download <= file_name \ mol_link_current <= current false + rel <= relation \ sub /$mol_view_content <= title arg * diff --git a/link/source/source.view.tree b/link/source/source.view.tree index ef64621250f..9c08be085a2 100644 --- a/link/source/source.view.tree +++ b/link/source/source.view.tree @@ -1,4 +1,4 @@ $mol_link_source $mol_link hint @ \Source code sub / - <= Icon $mol_icon_github_circle + <= Icon $mol_icon_script_text diff --git a/locale/locale.ts b/locale/locale.ts index 4ef4ec21f5d..c033917fe26 100644 --- a/locale/locale.ts +++ b/locale/locale.ts @@ -54,11 +54,11 @@ namespace $ { const en = this.texts( 'en' )[ key ] if( !en ) return key - try { - return $mol_wire_sync( $hyoo_lingua_translate ).call( this.$, lang, en ) - } catch( error ) { - $mol_fail_log( error ) - } + // try { + // return $mol_wire_sync( $hyoo_lingua_translate ).call( this.$, lang, en ) + // } catch( error ) { + // $mol_fail_log( error ) + // } return en } diff --git a/log3/log3.web.ts b/log3/log3.web.ts index 56aff5faa95..898e8535884 100644 --- a/log3/log3.web.ts +++ b/log3/log3.web.ts @@ -17,7 +17,7 @@ namespace $ { const chunks = Object.values( event ) for( let i = 0 ; i < chunks.length ; ++i ) { - tpl += ( typeof chunks[i] === 'string' ) ? ' ⦙ %s' : ' ⦙ %o' + tpl += ( typeof chunks[i] === 'string' ) ? ' ▫ %s' : ' ▫ %o' } const style = `color:${color};font-weight:bolder` diff --git a/mutable/demo/demo.view.tree b/mutable/demo/demo.view.tree new file mode 100644 index 00000000000..b10afa95243 --- /dev/null +++ b/mutable/demo/demo.view.tree @@ -0,0 +1,27 @@ +$mol_mutable_demo $mol_example_code + code? \ + \const articles_immutable = { + \ hello: { + \ title: 'Hello, World', + \ tags: [ 'javascript', 'immutablity' ], + \ author: { + \ name: 'Jin', + \ }, + \ }, + \} + \ + \const articles_mutable = $mol_mutable( articles_immutable ) + \ + \articles_mutable.hello.title( prev => prev + '!' ) + \articles_mutable.hello.tags( prev => [ ... prev, 'hello' ] ) + \articles_mutable.hello.author.name( ()=> 'John' ) + \articles_mutable.bye( ()=> ({ + \ title: 'Bye, World!', + \ tags: [], + \ author: null, + \}) ) + \ + \const articles_new = articles_mutable() + aspects / + \Mutable + \Array diff --git a/object2/object2.ts b/object2/object2.ts index 3cd8c4d5457..df48ad7b109 100644 --- a/object2/object2.ts +++ b/object2/object2.ts @@ -31,18 +31,22 @@ namespace $ { } static toString() { - if( Symbol.toStringTag in this ) return ( this as any )[ Symbol.toStringTag ] as string - return this.name + return ( this as any )[ Symbol.toStringTag ] || this.$.$mol_func_name( this ) + } + + static toJSON() { + return this.toString() } destructor() { } + static destructor() { } //[ Symbol.toPrimitive ]( hint: string ) { // return hint === 'number' ? this.valueOf() : this.toString() //} toString(): string { - return this[ Symbol.toStringTag ] || this.constructor.name + '()' + return this[ Symbol.toStringTag ] || this.constructor.name + '<>' } toJSON(): any { diff --git a/offline/offline.web.ts b/offline/offline.web.ts index 326b3f0fc35..267027ce0fe 100644 --- a/offline/offline.web.ts +++ b/offline/offline.web.ts @@ -19,7 +19,11 @@ namespace $ { caches.delete( '$mol_offline' ) ;( self as any ).clients.claim() - console.info( '$mol_offline activated' ) + + $$.$mol_log3_done({ + place: '$mol_offline', + message: 'Activated', + }) } ) @@ -39,9 +43,9 @@ namespace $ { ) } - if( request.method !== 'GET' || !/^https?:/.test( request.url ) ) { - return event.respondWith( fetch( request ) ) - } + if( request.method !== 'GET' ) return + if( !/^https?:/.test( request.url ) ) return + if( /\?/.test( request.url ) ) return const fresh = fetch( event.request ).then( response => { diff --git a/owning/owning.ts b/owning/owning.ts index 9239c285f3e..3c7aefaf63e 100644 --- a/owning/owning.ts +++ b/owning/owning.ts @@ -9,7 +9,7 @@ namespace $ { } { try { if( !having ) return false - if( typeof having !== 'object' ) return false + if( typeof having !== 'object' && typeof having !== 'function' ) return false if( having instanceof $mol_delegate ) return false if( typeof (having as any)['destructor'] !== 'function' ) return false return true diff --git a/page/page.view.css.ts b/page/page.view.css.ts index c867057778f..30fe631785b 100644 --- a/page/page.view.css.ts +++ b/page/page.view.css.ts @@ -87,6 +87,11 @@ namespace $.$$ { Body_content: { padding: $mol_gap.block , + flex: { + direction: 'column', + shrink: 1, + grow: 1, + }, justify: { self: 'stretch', }, diff --git a/page/page.view.tree b/page/page.view.tree index e62d693b0f8..d383c52e94d 100644 --- a/page/page.view.tree +++ b/page/page.view.tree @@ -18,8 +18,8 @@ $mol_page $mol_view <= Body $mol_scroll scroll_top? => body_scroll_top? sub <= body_content / - <= Body_content $mol_list - rows <= body /$mol_view + <= Body_content $mol_view + sub <= body /$mol_view <= Foot $mol_view dom_name \footer sub <= foot /$mol_view diff --git a/pick/pick.view.tree b/pick/pick.view.tree index 8bf59a95186..561282fca2c 100644 --- a/pick/pick.view.tree +++ b/pick/pick.view.tree @@ -7,6 +7,7 @@ $mol_pick $mol_pop minimal_height 40 enabled <= trigger_enabled true checked? <=> showed? + clicks? <=> clicks? null sub <= trigger_content /$mol_view_content <= title hint <= hint \ diff --git a/picture/picture.ts b/picture/picture.ts index f23a5ecb51a..b6c5842fd7a 100644 --- a/picture/picture.ts +++ b/picture/picture.ts @@ -13,6 +13,10 @@ namespace $ { return this.canvas.getContext( '2d' ) } + get bitmap() { + return this.context!.getImageData( 0, 0, this.canvas.width, this.canvas.height ) + } + @ $mol_action static fit( image: Exclude< CanvasImageSource, VideoFrame > | Blob | string, diff --git a/pop/pop.view.css b/pop/pop.view.css index 483335a86bf..488418e35c4 100644 --- a/pop/pop.view.css +++ b/pop/pop.view.css @@ -20,6 +20,7 @@ flex-direction: column; max-width: 80vw; max-height: 80vw; + contain: paint; } :where( [mol_pop_bubble] > * ) { diff --git a/promise/like/like.ts b/promise/like/like.ts index 0de32c684cc..d7372e9d951 100644 --- a/promise/like/like.ts +++ b/promise/like/like.ts @@ -1,7 +1,7 @@ namespace $ { export function $mol_promise_like( val: any ): val is Promise { - return val && typeof val.then === 'function' + return val && typeof val === 'object' && 'then' in val && typeof val.then === 'function' } } diff --git a/reconcile/reconcile.test.tsx b/reconcile/reconcile.test.tsx index 3fb8b1f8b9a..39e9d2f877e 100644 --- a/reconcile/reconcile.test.tsx +++ b/reconcile/reconcile.test.tsx @@ -27,7 +27,7 @@ namespace $ { }, }) - $mol_assert_dom( list, + $mol_assert_equal( list,

a

b

c

@@ -59,7 +59,7 @@ namespace $ { }, }) - $mol_assert_dom( list, + $mol_assert_equal( list,

a

b

X

@@ -93,7 +93,7 @@ namespace $ { }, }) - $mol_assert_dom( list, + $mol_assert_equal( list,

a

b

c

@@ -128,7 +128,7 @@ namespace $ { }, }) - $mol_assert_dom( list, + $mol_assert_equal( list,

A

B

C

@@ -161,7 +161,7 @@ namespace $ { }, }) - $mol_assert_dom( list, + $mol_assert_equal( list,

a

X

Y

diff --git a/span/span.test.ts b/span/span.test.ts index 27c86e757cc..e5e8823280a 100644 --- a/span/span.test.ts +++ b/span/span.test.ts @@ -46,16 +46,16 @@ namespace $ { 'slice span - out of range'( $ ) { const span = new $mol_span('test.ts', '', 1, 3, 5) - $mol_assert_fail(() => span.slice(-1, 3)) - $mol_assert_fail(() => span.slice(1, 6)) - $mol_assert_fail(() => span.slice(1, 10)) + $mol_assert_fail( ()=> span.slice(-1, 3), `End value '3' can't be less than begin value (test.ts#1:3/5)` ) + $mol_assert_fail( ()=> span.slice(1, 6), `End value '6' out of range (test.ts#1:3/5)` ) + $mol_assert_fail( ()=> span.slice(1, 10), `End value '10' out of range (test.ts#1:3/5)` ) }, 'error handling'( $ ) { const span = new $mol_span('test.ts', '', 1, 3, 4) - const error = span.error('Some error\n') + const error = span.error('Some error') - $mol_assert_equal(error.message, 'Some error\ntest.ts#1:3/4') + $mol_assert_equal(error.message, 'Some error (test.ts#1:3/4)') } } ) diff --git a/span/span.ts b/span/span.ts index f7f3f555dbc..d2b14090a76 100644 --- a/span/span.ts +++ b/span/span.ts @@ -47,7 +47,7 @@ namespace $ { /** Makes new error for this span. */ error( message : string , Class = Error ) { - return new Class( `${message}${this}` ) + return new Class( `${message} (${this})` ) } /** Makes new span for same uri. */ @@ -67,9 +67,9 @@ namespace $ { if( begin < 0 ) begin += len if( end < 0 ) end += len - if (begin < 0 || begin > len) this.$.$mol_fail(`Begin value '${begin}' out of range ${this}`) - if (end < 0 || end > len) this.$.$mol_fail(`End value '${end}' out of range ${this}`) - if (end < begin) this.$.$mol_fail(`End value '${end}' can't be less than begin value ${this}`) + if (begin < 0 || begin > len) this.$.$mol_fail( this.error( `Begin value '${begin}' out of range`, RangeError ) ) + if (end < 0 || end > len) this.$.$mol_fail( this.error( `End value '${end}' out of range`, RangeError ) ) + if (end < begin) this.$.$mol_fail( this.error( `End value '${end}' can't be less than begin value`, RangeError ) ) return this.span( this.row , this.col + begin , end - begin ) } diff --git a/storage/storage.node.ts b/storage/storage.node.ts deleted file mode 100644 index 42801597e7e..00000000000 --- a/storage/storage.node.ts +++ /dev/null @@ -1,23 +0,0 @@ -namespace $ { - export class $mol_storage extends $mol_object2 { - - @ $mol_mem - static native() { - return null as any - } - - @ $mol_mem - static persisted( next?: boolean ): boolean { - return false - } - - static estimate() { - return 0 - } - - static dir() { - return null as any as FileSystemDirectoryHandle - } - - } -} diff --git a/storage/storage.web.ts b/storage/storage.ts similarity index 56% rename from storage/storage.web.ts rename to storage/storage.ts index 91536888814..9f9f5170086 100644 --- a/storage/storage.web.ts +++ b/storage/storage.ts @@ -3,7 +3,12 @@ namespace $ { @ $mol_mem static native() { - return this.$.$mol_dom_context.navigator.storage + return this.$.$mol_dom_context.navigator.storage ?? { // exists only in secure context + persisted: async ()=> false, + persist: async ()=> false, + estimate: async ()=> ({}), + getDirectory: async ()=> null! as FileSystemHandle, + } as StorageManager } @ $mol_mem @@ -16,11 +21,11 @@ namespace $ { const native = this.native() if( next && !$mol_mem_cached( ()=> this.persisted() ) ) { native.persist().then( actual => { - + setTimeout( ()=> this.persisted( actual, 'cache' ), 5000 ) - if( actual ) this.$.$mol_log3_rise({ place: this, message: `Persist` }) - else this.$.$mol_log3_fail({ place: this, message: `Non persist` }) + if( actual ) this.$.$mol_log3_done({ place: `$mol_storage`, message: `Persist: Yes` }) + else this.$.$mol_log3_fail({ place: `$mol_storage`, message: `Persist: No` }) } ) } @@ -29,7 +34,7 @@ namespace $ { } static estimate() { - return $mol_wire_sync( this.native() ).estimate() + return $mol_wire_sync( this.native() ?? {} ).estimate() } static dir() { diff --git a/style/func/func.ts b/style/func/func.ts index f1ee5544104..f8a8935a52d 100644 --- a/style/func/func.ts +++ b/style/func/func.ts @@ -6,14 +6,21 @@ namespace $ { | 'rgba' | 'var' | 'clamp' - | 'url' | 'scale' | 'cubic-bezier' | 'linear' | 'steps' + | $mol_style_func_image | $mol_style_func_filter - export type $mol_style_func_filter = 'blur' + export type $mol_style_func_image = + | 'url' + | 'linear-gradient' + | 'radial-gradient' + | 'conic-gradient' + + export type $mol_style_func_filter = + | 'blur' | 'brightness' | 'contrast' | 'drop-shadow' @@ -45,6 +52,10 @@ namespace $ { prefix() { return this.name + '(' } postfix() { return ')' } + static linear_gradient< Value >( value : Value ) { + return new $mol_style_func( 'linear-gradient' , value ) + } + static calc< Value >( value : Value ) { return new $mol_style_func( 'calc' , value ) } @@ -70,9 +81,9 @@ namespace $ { } static clamp( - min: $mol_style_unit< any >, - mid: $mol_style_unit< any >, - max: $mol_style_unit< any >, + min: $mol_style_unit_str< any >, + mid: $mol_style_unit_str< any >, + max: $mol_style_unit_str< any >, ) { return new $mol_style_func( 'clamp', @@ -99,7 +110,7 @@ namespace $ { } static linear( - ...breakpoints : Array]> + ...breakpoints : Array]> ){ return new $mol_style_func( "linear", @@ -126,23 +137,23 @@ namespace $ { return new $mol_style_func( 'steps', [ value, step_position ] ) } - static blur(value?: $mol_style_unit<$mol_style_unit_length>){ + static blur(value?: $mol_style_unit_str<$mol_style_unit_length>){ return new $mol_style_func( 'blur', value ?? "" ); } - static brightness(value?: number | $mol_style_unit<'%'>){ + static brightness(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'brightness', value ?? "" ); } - static contrast(value?: number | $mol_style_unit<'%'>){ + static contrast(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'contrast', value ?? "" ); } static drop_shadow( color: $mol_style_properties_color, - x_offset: $mol_style_unit<$mol_style_unit_length>, - y_offset: $mol_style_unit<$mol_style_unit_length>, - blur_radius?: $mol_style_unit<$mol_style_unit_length> + x_offset: $mol_style_unit_str<$mol_style_unit_length>, + y_offset: $mol_style_unit_str<$mol_style_unit_length>, + blur_radius?: $mol_style_unit_str<$mol_style_unit_length> ) { return new $mol_style_func( "drop-shadow", @@ -152,27 +163,27 @@ namespace $ { ); } - static grayscale(value?: number | $mol_style_unit<'%'>){ + static grayscale(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'grayscale', value ?? "" ); } - static hue_rotate(value?: 0 | $mol_style_unit<$mol_style_unit_angle>){ + static hue_rotate(value?: 0 | $mol_style_unit_str<$mol_style_unit_angle>){ return new $mol_style_func( 'hue-rotate', value ?? "") } - static invert(value?: number | $mol_style_unit<'%'>){ + static invert(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'invert', value ?? "" ); } - static opacity(value?: number | $mol_style_unit<'%'>){ + static opacity(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'opacity', value ?? "" ); } - static sepia(value?: number | $mol_style_unit<'%'>){ + static sepia(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'sepia', value ?? "" ); } - static saturate(value?: number | $mol_style_unit<'%'>){ + static saturate(value?: number | $mol_style_unit_str<'%'>){ return new $mol_style_func( 'saturate', value ?? "" ); } diff --git a/style/guard/guard.ts b/style/guard/guard.ts index 382c099c141..e2baacbb152 100644 --- a/style/guard/guard.ts +++ b/style/guard/guard.ts @@ -20,6 +20,7 @@ namespace $ { export type $mol_style_guard< View extends $mol_view , Config > = & { [ key in Keys< View > ]?: unknown } + & $mol_style_properties & { [ key in keyof Config ] @@ -35,6 +36,9 @@ namespace $ { : key extends '@media' ? Medias< View , Config[key] > + : key extends `[${string}]` + ? { [ val in keyof Config[key] ]: $mol_style_guard< View , Config[key][val] > } + : key extends `--${string}` ? any diff --git a/style/properties/properties.ts b/style/properties/properties.ts index 9c690bb1cfa..01b4ceee826 100644 --- a/style/properties/properties.ts +++ b/style/properties/properties.ts @@ -292,7 +292,7 @@ namespace $ { * @see https://developer.mozilla.org/ru/docs/Web/CSS/background-image */ image?: - | readonly( readonly [ $mol_style_func<'url'> | string&{} ] )[] + | readonly( readonly [ $mol_style_func< $mol_style_func_image > | string&{} ] )[] | 'none' | Common /** diff --git a/style/sheet/sheet.ts b/style/sheet/sheet.ts index 49460c2738b..b49e61b4632 100644 --- a/style/sheet/sheet.ts +++ b/style/sheet/sheet.ts @@ -102,6 +102,15 @@ namespace $ { } + } else if( key[0] === '[' && key[key.length-1] === ']' ) { + + const attr = key.slice( 1, -1 ) + const vals = config[ key as any ] as any as Record< string, any > + + for( let val in vals ) { + make_class( selector( prefix , path ) + ':where([' + attr + '=' + JSON.stringify( val ) + '])' , [] , vals[val] ) + } + } else { make_class( selector( prefix , path ) + key , [] , (config as any)[key] ) diff --git a/style/unit/unit.ts b/style/unit/unit.ts index fb9ae6f6cc0..eda68019d1f 100644 --- a/style/unit/unit.ts +++ b/style/unit/unit.ts @@ -11,6 +11,7 @@ namespace $ { export type $mol_style_unit_time = 's' | 'ms' export type $mol_style_unit_any = $mol_style_unit_length | $mol_style_unit_angle | $mol_style_unit_time + export type $mol_style_unit_str< Quanity extends $mol_style_unit_any > = `${number}${Quanity}` /** * CSS Units diff --git a/syntax2/syntax2.ts b/syntax2/syntax2.ts index a5179993d78..516f67cdbbc 100644 --- a/syntax2/syntax2.ts +++ b/syntax2/syntax2.ts @@ -46,7 +46,7 @@ namespace $ { if( start === end ) throw new Error( 'Empty token' ) var prefix = found[ 1 ] - if( prefix ) handle( '' , prefix , [] , start ) + if( prefix ) handle( '' , prefix , [ prefix ] , start ) var suffix = found[ 2 ] if( !suffix ) continue diff --git a/text/text/text.view.ts b/text/text/text.view.ts index 8ac035bd614..596c7370433 100644 --- a/text/text/text.view.ts +++ b/text/text/text.view.ts @@ -41,7 +41,7 @@ namespace $.$$ { @ $mol_mem param() { - return this.toString().replace( /^.*?\)\./, '' ).replace( /[()]/g, '' ) + return this.toString().replace( /^.*?[\)>]\./, '' ).replace( /[(<>)]/g, '' ) } @ $mol_mem_key @@ -151,6 +151,11 @@ namespace $.$$ { return $mol_dom_context.document.location.href } + @ $mol_mem + uri_base_abs() { + return new URL( this.uri_base() , $mol_dom_context.document.location.href ) + } + @ $mol_mem_key uri_resolve( uri: string ) { @@ -172,7 +177,7 @@ namespace $.$$ { try { - const url = new URL( uri , this.uri_base() ) + const url = new URL( uri , this.uri_base_abs() ) return url.toString() } catch( error ) { diff --git a/textarea/textarea.view.ts b/textarea/textarea.view.ts index b6574376e03..a8e3f349b2d 100644 --- a/textarea/textarea.view.ts +++ b/textarea/textarea.view.ts @@ -68,6 +68,7 @@ namespace $.$$ { if( !symbol ) return + event.preventDefault() document.execCommand( 'insertText', false, symbol ) } @@ -96,9 +97,9 @@ namespace $.$$ { default : return } + event.preventDefault() + } - - event.preventDefault() } diff --git a/time/moment/moment.ts b/time/moment/moment.ts index 0438c66bc8f..8818e606e59 100644 --- a/time/moment/moment.ts +++ b/time/moment/moment.ts @@ -218,6 +218,14 @@ namespace $ { [ Symbol.toPrimitive ]( mode: 'default' | 'number' | 'string' ) { return mode === 'number' ? this.valueOf() : this.toString() } + + [ $mol_dev_format_head ]() { + return $mol_dev_format_span( {}, + $mol_dev_format_native( this ), + ' ', + $mol_dev_format_accent( this.toString( 'YYYY-MM-DD hh:mm:ss.sss Z' ) ), + ) + } /// Mnemonics: /// * single letter for numbers: M - month number, D - day of month. diff --git a/tree2/bin/bin.ts b/tree2/bin/bin.ts index 33939414d4e..9f2ba212cde 100644 --- a/tree2/bin/bin.ts +++ b/tree2/bin/bin.ts @@ -5,8 +5,8 @@ namespace $ { } export function $mol_tree2_bin_from_bytes( - bytes : ArrayLike< number > , - span : $mol_span , + bytes : ArrayLike< number >, + span = $mol_span.unknown, ) { return $mol_tree2.list( Array.from( bytes , code => { @@ -15,7 +15,10 @@ namespace $ { } - export function $mol_tree2_bin_from_string( str : string , span : $mol_span ) { + export function $mol_tree2_bin_from_string( + str : string, + span = $mol_span.unknown, + ) { return $mol_tree2_bin_from_bytes( [ ... new TextEncoder().encode( str ) ] , span ) } diff --git a/type/erase/erase.ts b/type/erase/erase.ts new file mode 100644 index 00000000000..a18f90ca737 --- /dev/null +++ b/type/erase/erase.ts @@ -0,0 +1,10 @@ +namespace $ { + + export type $mol_type_erase< + Class extends { new(): any }, + Keys extends keyof InstanceType< Class > + > = Omit< Class, 'prototype' > & { + new(): Omit< InstanceType< Class >, Keys > + } + +} diff --git a/view/tree2/to/js/js.bidi.test.ts b/view/tree2/to/js/js.bidi.test.ts index 597a04b3a4c..9bd0f5ab774 100644 --- a/view/tree2/to/js/js.bidi.test.ts +++ b/view/tree2/to/js/js.bidi.test.ts @@ -186,7 +186,7 @@ namespace $ { a!? $mol_object expanded <=> cell_expanded!? null `) - }) + }, `Cannot destructure property 'name' of 'prop_parts(...)' as it is undefined. at ?#3:7/3` ) }, 'Bidi bind with default object'( $ ) { diff --git a/view/tree2/to/js/js.right.test.ts b/view/tree2/to/js/js.right.test.ts index 3c159ee5291..a5ac60e9b59 100644 --- a/view/tree2/to/js/js.right.test.ts +++ b/view/tree2/to/js/js.right.test.ts @@ -73,8 +73,9 @@ namespace $ { { some: 123 } ) - $mol_assert_unique( - bar.Cls(1).a(), bar.b(2) + $mol_assert_equal( + bar.Cls(1).a() === bar.b(2), + false, ) } diff --git a/view/tree2/to/js/js.simple.test.ts b/view/tree2/to/js/js.simple.test.ts index ca2786a91f8..39f69aa6aa5 100644 --- a/view/tree2/to/js/js.simple.test.ts +++ b/view/tree2/to/js/js.simple.test.ts @@ -89,14 +89,14 @@ namespace $ { Foo $mol_object a!? null `) - }) + }, `Cannot destructure property 'name' of 'prop_parts(...)' as it is undefined. at ?#3:7/3` ) $mol_assert_fail(() => { run(` Foo $mol_object b! 1 `) - }) + }, `Cannot destructure property 'name' of 'prop_parts(...)' as it is undefined. at ?#3:7/2` ) }, 'two classes'( $ ) { diff --git a/view/view/view.ts b/view/view/view.ts index 490af606f4b..ecc16398c84 100644 --- a/view/view/view.ts +++ b/view/view/view.ts @@ -161,8 +161,9 @@ namespace $ { } } + @ $mol_memo.method dom_id() { - return this.toString() + return this.toString().replace( //g, ')' ) } dom_node_external( next?: Element) { diff --git a/wire/atom/atom.ts b/wire/atom/atom.ts index 95e185fd24b..9625751fc93 100644 --- a/wire/atom/atom.ts +++ b/wire/atom/atom.ts @@ -16,7 +16,7 @@ namespace $ { task: ( this: Host, ... args: Args )=> Result, ): $mol_wire_atom< Host, Args, Result > { - const field = task.name + '()' + const field = task.name + '<>' const existen = Object.getOwnPropertyDescriptor( host ?? task, field )?.value if( existen ) return existen @@ -40,20 +40,21 @@ namespace $ { key: Args[0], ): $mol_wire_atom< Host, Args, Result > { - const field = task.name + '()' + const field = task.name + '<>' let dict = Object.getOwnPropertyDescriptor( host ?? task, field )?.value const prefix = (host as any)?.[ Symbol.toStringTag ] ?? ( host instanceof Function ? $$.$mol_func_name( host ) : host ) - const id = `${ prefix }.${ task.name }(${ $mol_key( key ).replace( /^"|"$/g, "'" ) })` + const key_str = $mol_key( key ) if( dict ) { - const existen = dict.get( id ) + const existen = dict.get( key_str ) if( existen ) return existen } else { dict = ( host as any ?? task )[ field ] = new Map() } + const id = `${ prefix }.${ task.name }<${ key_str.replace( /^"|"$/g, "'" ) }>` const fiber = new $mol_wire_atom( id, task, host, [ key ] as any as Args ) - dict.set( id, fiber ) + dict.set( key_str, fiber ) return fiber } @@ -128,7 +129,7 @@ namespace $ { if( this.pub_from === 0 ) { ;( this.host as any ?? this.task )[ this.field() ] = null } else { - ;( this.host as any ?? this.task )[ this.field() ].delete((this as any)[ Symbol.toStringTag ] ) + ;( this.host as any ?? this.task )[ this.field() ].delete( $mol_key( this.args[0] ) ) } } diff --git a/wire/fiber/fiber.ts b/wire/fiber/fiber.ts index 1a3c126d13f..5c595e8c417 100644 --- a/wire/fiber/fiber.ts +++ b/wire/fiber/fiber.ts @@ -83,7 +83,7 @@ namespace $ { } field() { - return this.task.name + '()' + return this.task.name + '<>' } constructor( @@ -128,8 +128,12 @@ namespace $ { }[ this.cursor ] ?? this.cursor.toString() return $mol_dev_format_div( {}, - $mol_dev_format_native( this ), - $mol_dev_format_shade( cursor + ' ' ), + $mol_owning_check( this, this.cache ) + ? $mol_dev_format_auto({ + [ $mol_dev_format_head ]: ()=> $mol_dev_format_shade( cursor ), + [ $mol_dev_format_body ]: ()=> $mol_dev_format_native( this ), + }) + : $mol_dev_format_shade( $mol_dev_format_native( this ), cursor ), $mol_dev_format_auto( this.cache ), ) diff --git a/wire/plex/plex.test.ts b/wire/plex/plex.test.ts index 646bd904662..4912af1196e 100644 --- a/wire/plex/plex.test.ts +++ b/wire/plex/plex.test.ts @@ -98,8 +98,8 @@ namespace $ { } - $mol_assert_equal( `${ App.like(123) }` , 'App.like(123)' ) - $mol_assert_equal( `${ App.relation([123,[456]]) }` , 'App.relation([123,[456]])' ) + $mol_assert_equal( `${ App.like(123) }` , 'App.like<123>' ) + $mol_assert_equal( `${ App.relation([123,[456]]) }` , 'App.relation<[123,[456]]>' ) } , diff --git a/wire/solid/solid.ts b/wire/solid/solid.ts index b73614da2fa..ce8e3e2f2c1 100644 --- a/wire/solid/solid.ts +++ b/wire/solid/solid.ts @@ -4,7 +4,8 @@ namespace $ { * Disable reaping of current subscriber */ export function $mol_wire_solid() { - const current = $mol_wire_auto() + let current = $mol_wire_auto() as $mol_wire_fiber< any, any, any > + if( current!.temp ) current = current!.host if( current!.reap !== nothing ) { current?.sub_on( sub, sub.data.length ) } diff --git a/wire/solo/solo.test.ts b/wire/solo/solo.test.ts index ef7e33f8411..a1ab8d7c25f 100644 --- a/wire/solo/solo.test.ts +++ b/wire/solo/solo.test.ts @@ -470,7 +470,7 @@ namespace $ { $mol_assert_equal( App.result() , 1 ) App.condition( true ) - $mol_assert_fail( ()=> App.result() ) + $mol_assert_fail( ()=> App.result(), 'test error' ) App.condition( false ) $mol_assert_equal( App.result() , 1 ) @@ -609,7 +609,7 @@ namespace $ { } - $mol_assert_equal( `${ App.title() }` , 'App.title()' ) + $mol_assert_equal( `${ App.title() }` , 'App.title<>' ) } , diff --git a/wire/task/task.ts b/wire/task/task.ts index e8bb6575d2d..f9488c7382b 100644 --- a/wire/task/task.ts +++ b/wire/task/task.ts @@ -31,12 +31,20 @@ namespace $ { return existen } + const next = new $mol_wire_task( `${ (host as any)?.[ Symbol.toStringTag ] ?? host }.${ task.name }<#>`, task, host, args ) + // Disabled because non-idempotency is required for try-catch - // if( existen && sub instanceof $mol_wire_task ) { - // $mol_fail( new Error( `$mol_wire_task detects nonidempotency\n${existen}` ) ) - // } + if( existen?.temp ) { + $$.$mol_log3_warn({ + place: '$mol_wire_task', + message: `Non idempotency`, + existen, + next, + hint: 'Ignore it', + }) + } - return new $mol_wire_task( `${ (host as any)?.[ Symbol.toStringTag ] ?? host }.${ task.name }(#)`, task, host, args ) + return next } }