mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
reload app_window on every code execution
This commit is contained in:
96
src/cmd.js
96
src/cmd.js
@@ -1,5 +1,5 @@
|
||||
import {map_object, map_find, filter_object, collect_nodes_with_parents, uniq}
|
||||
from './utils.js'
|
||||
import {map_object, map_find, filter_object, collect_nodes_with_parents, uniq,
|
||||
set_is_eq} from './utils.js'
|
||||
import {
|
||||
is_eq, is_child, ancestry, ancestry_inc, map_tree,
|
||||
find_leaf, find_fn_by_location, find_node, find_error_origin_node,
|
||||
@@ -69,14 +69,19 @@ const apply_eval_result = (state, eval_result) => {
|
||||
}
|
||||
}
|
||||
|
||||
const run_code = (s, dirty_files) => {
|
||||
const run_code = (s, globals) => {
|
||||
const is_globals_eq = s.globals == null
|
||||
? globals == null
|
||||
: set_is_eq(s.globals, globals)
|
||||
|
||||
const parse_result = load_modules(s.entrypoint, module => {
|
||||
if(dirty_files != null && dirty_files.includes(module)) {
|
||||
if(s.dirty_files != null && s.dirty_files.has(module)) {
|
||||
return s.files[module]
|
||||
}
|
||||
|
||||
if(s.parse_result != null) {
|
||||
// If globals change, then errors for using undeclared identifiers may be
|
||||
// no longer valid. Do not use cache
|
||||
if(is_globals_eq) {
|
||||
const result = s.parse_result.cache[module]
|
||||
if(result != null) {
|
||||
return result
|
||||
@@ -87,10 +92,18 @@ const run_code = (s, dirty_files) => {
|
||||
return s.files[module]
|
||||
}
|
||||
|
||||
}, s.globals)
|
||||
}, globals)
|
||||
|
||||
const dirty_files = new Set(
|
||||
[...(s.dirty_files ?? new Set())].filter(file =>
|
||||
parse_result.modules[file] == null
|
||||
)
|
||||
)
|
||||
|
||||
const state = {
|
||||
...s,
|
||||
dirty_files,
|
||||
globals,
|
||||
parse_result,
|
||||
calltree: null,
|
||||
modules: null,
|
||||
@@ -121,15 +134,7 @@ const run_code = (s, dirty_files) => {
|
||||
.map(i => i.node.full_import_path)
|
||||
)
|
||||
|
||||
if(
|
||||
external_imports.length != 0
|
||||
&&
|
||||
(
|
||||
state.external_imports_cache == null
|
||||
||
|
||||
external_imports.some(i => state.external_imports_cache[i] == null)
|
||||
)
|
||||
) {
|
||||
if(external_imports.length != 0) {
|
||||
// Trigger loading of external modules
|
||||
return {...state,
|
||||
loading_external_imports_state: {
|
||||
@@ -138,18 +143,8 @@ const run_code = (s, dirty_files) => {
|
||||
}
|
||||
} else {
|
||||
// Modules were loaded and cached, proceed
|
||||
return external_imports_loaded(
|
||||
state,
|
||||
state,
|
||||
state.external_imports_cache == null
|
||||
? null
|
||||
: filter_object(
|
||||
state.external_imports_cache,
|
||||
(module_name, module) => external_imports.includes(module_name)
|
||||
),
|
||||
)
|
||||
return external_imports_loaded(state, state)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const external_imports_loaded = (
|
||||
@@ -168,7 +163,6 @@ const external_imports_loaded = (
|
||||
|
||||
const state = {
|
||||
...s,
|
||||
external_imports_cache: external_imports,
|
||||
loading_external_imports_state: null
|
||||
}
|
||||
|
||||
@@ -204,6 +198,7 @@ const external_imports_loaded = (
|
||||
state.on_deferred_call,
|
||||
state.calltree_changed_token,
|
||||
state.io_trace,
|
||||
state.storage,
|
||||
)
|
||||
|
||||
if(result.then != null) {
|
||||
@@ -252,10 +247,12 @@ const eval_modules_finished = (state, prev_state, result) => {
|
||||
|
||||
const input = (state, code, index) => {
|
||||
const files = {...state.files, [state.current_module]: code}
|
||||
const next = run_code(
|
||||
set_cursor_position({...state, files}, index),
|
||||
[state.current_module]
|
||||
)
|
||||
const with_files = {
|
||||
...state,
|
||||
files,
|
||||
dirty_files: new Set([...(state.dirty_files ?? []), state.current_module])
|
||||
}
|
||||
const next = set_cursor_position(with_files, index)
|
||||
const effect_save = {
|
||||
type: 'write',
|
||||
args: [
|
||||
@@ -519,12 +516,10 @@ const change_current_module = (state, current_module) => {
|
||||
}
|
||||
|
||||
const change_entrypoint = (state, entrypoint, current_module = entrypoint) => {
|
||||
return run_code(
|
||||
{...state,
|
||||
entrypoint,
|
||||
current_module,
|
||||
}
|
||||
)
|
||||
return {...state,
|
||||
entrypoint,
|
||||
current_module,
|
||||
}
|
||||
}
|
||||
|
||||
const change_html_file = (state, html_file) => {
|
||||
@@ -852,7 +847,7 @@ const on_deferred_call = (state, call, calltree_changed_token, logs) => {
|
||||
}
|
||||
|
||||
const clear_io_trace = state => {
|
||||
return run_code({...state, io_trace: null})
|
||||
return {...state, io_trace: null}
|
||||
}
|
||||
|
||||
const load_files = (state, dir) => {
|
||||
@@ -892,7 +887,7 @@ const apply_entrypoint_settings = (state, entrypoint_settings) => {
|
||||
const load_dir = (state, dir, has_file_system_access, entrypoint_settings) => {
|
||||
// Clear parse cache and rerun code
|
||||
const with_dir = load_files(state, dir)
|
||||
return run_code({
|
||||
return {
|
||||
...(
|
||||
entrypoint_settings == null
|
||||
? with_dir
|
||||
@@ -904,30 +899,15 @@ const load_dir = (state, dir, has_file_system_access, entrypoint_settings) => {
|
||||
// remove cache. We have to clear cache because imports of modules that are
|
||||
// not available because project_dir is not available have errors and the
|
||||
// errors are cached
|
||||
parse_result: null,
|
||||
|
||||
external_imports_cache: null,
|
||||
})
|
||||
parse_result: {...state.parse_result, cache: {}},
|
||||
}
|
||||
}
|
||||
|
||||
const create_file = (state, dir, current_module) => {
|
||||
return {...load_dir(state, dir, true), current_module}
|
||||
}
|
||||
|
||||
const open_app_window = (state, globals) => {
|
||||
// After we reopen run window, we should reload external modules in the
|
||||
// context of new window. Clear external_imports_cache
|
||||
return run_code({
|
||||
...state,
|
||||
globals,
|
||||
external_imports_cache: null,
|
||||
// Bust parse result cache because list of globals may change
|
||||
parse_result: null,
|
||||
// Clear io trace because promises in io_trace become invalid after their
|
||||
// window close
|
||||
io_trace: null,
|
||||
})
|
||||
}
|
||||
const open_app_window = state => ({...state, storage: new Map()})
|
||||
|
||||
const get_initial_state = (state, entrypoint_settings, cursor_pos = 0) => {
|
||||
const with_files = state.project_dir == null
|
||||
@@ -938,6 +918,7 @@ const get_initial_state = (state, entrypoint_settings, cursor_pos = 0) => {
|
||||
|
||||
return {
|
||||
...with_settings,
|
||||
storage: new Map(),
|
||||
cursor_position_by_file: {[with_settings.current_module]: cursor_pos},
|
||||
}
|
||||
}
|
||||
@@ -945,6 +926,7 @@ const get_initial_state = (state, entrypoint_settings, cursor_pos = 0) => {
|
||||
export const COMMANDS = {
|
||||
get_initial_state,
|
||||
input,
|
||||
run_code,
|
||||
open_app_window,
|
||||
load_dir,
|
||||
create_file,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {exec, get_state} from '../index.js'
|
||||
import {exec, get_state, exec_and_reload_app_window} from '../index.js'
|
||||
import {ValueExplorer} from './value_explorer.js'
|
||||
import {stringify_for_header} from '../value_explorer_utils.js'
|
||||
import {el} from './domutils.js'
|
||||
@@ -98,7 +98,7 @@ export class Editor {
|
||||
normalize_events(this.ace_editor, {
|
||||
on_change: () => {
|
||||
try {
|
||||
exec('input', this.ace_editor.getValue(), this.get_cursor_position())
|
||||
exec_and_reload_app_window('input', this.ace_editor.getValue(), this.get_cursor_position())
|
||||
} catch(e) {
|
||||
// Do not throw Error to ACE because it breaks typing
|
||||
console.error(e)
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
exec,
|
||||
get_state,
|
||||
open_directory,
|
||||
reload_app_window,
|
||||
exec_and_reload_app_window,
|
||||
close_directory,
|
||||
} from '../index.js'
|
||||
|
||||
@@ -20,16 +20,14 @@ export class Files {
|
||||
this.render(get_state())
|
||||
}
|
||||
|
||||
change_entrypoint(e) {
|
||||
const file = e.target.value
|
||||
exec('change_entrypoint', file)
|
||||
change_entrypoint(entrypoint, current_module) {
|
||||
exec_and_reload_app_window('change_entrypoint', entrypoint, current_module)
|
||||
this.ui.editor.focus()
|
||||
}
|
||||
|
||||
change_html_file(e) {
|
||||
const html_file = e.target.value
|
||||
exec('change_html_file', html_file)
|
||||
reload_app_window(get_state())
|
||||
exec_and_reload_app_window('change_html_file', html_file)
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +117,7 @@ export class Files {
|
||||
name: 'js_entrypoint',
|
||||
value: file.path,
|
||||
checked: state.entrypoint == file.path,
|
||||
change: e => this.change_entrypoint(e),
|
||||
change: e => this.change_entrypoint(e.target.value),
|
||||
click: e => e.stopPropagation(),
|
||||
})
|
||||
)
|
||||
@@ -210,9 +208,9 @@ export class Files {
|
||||
// Reload all files for simplicity
|
||||
open_dir(false).then(dir => {
|
||||
if(is_dir) {
|
||||
exec('load_dir', dir, true)
|
||||
exec_and_reload_app_window('load_dir', dir, true)
|
||||
} else {
|
||||
exec('create_file', dir, path)
|
||||
exec_and_reload_app_window('create_file', dir, path)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -229,7 +227,7 @@ export class Files {
|
||||
if(file.path == null) {
|
||||
// root of examples dir, do nothing
|
||||
} else if(file.path == '') {
|
||||
exec('change_entrypoint', '')
|
||||
this.change_entrypoint('')
|
||||
} else {
|
||||
const find_node = n =>
|
||||
n.path == file.path
|
||||
@@ -244,8 +242,7 @@ export class Files {
|
||||
// in examples mode, on click file we also change entrypoint for
|
||||
// simplicity
|
||||
const example = examples.find(e => e.path == example_dir.path)
|
||||
exec(
|
||||
'change_entrypoint',
|
||||
this.change_entrypoint(
|
||||
example.entrypoint,
|
||||
file.kind == 'directory' ? undefined : file.path
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {exec, get_state, open_app_window} from '../index.js'
|
||||
import {exec, get_state, open_app_window, exec_and_reload_app_window} from '../index.js'
|
||||
import {Editor} from './editor.js'
|
||||
import {Files} from './files.js'
|
||||
import {CallTree} from './calltree.js'
|
||||
@@ -73,7 +73,7 @@ export class UI {
|
||||
el('a', {
|
||||
'class': 'statusbar_action first',
|
||||
href: 'javascript: void(0)',
|
||||
click: () => exec('clear_io_trace')
|
||||
click: () => this.clear_io_trace(),
|
||||
},
|
||||
'Clear IO trace (F6)'
|
||||
),
|
||||
@@ -196,6 +196,10 @@ export class UI {
|
||||
}
|
||||
}
|
||||
|
||||
clear_io_trace() {
|
||||
exec_and_reload_app_window('clear_io_trace')
|
||||
}
|
||||
|
||||
open_app_window() {
|
||||
this.toggle_open_app_window_tooltip(false)
|
||||
localStorage.onboarding_open_app_window = true
|
||||
|
||||
@@ -421,6 +421,7 @@ export const eval_modules = (
|
||||
on_deferred_call,
|
||||
calltree_changed_token,
|
||||
io_trace,
|
||||
storage,
|
||||
) => {
|
||||
// TODO gensym __cxt, __trace, __trace_call, __calltree_node_by_loc,
|
||||
// __await_start, __await_finish, __Multiversion, __create_array, __create_object
|
||||
@@ -498,6 +499,8 @@ export const eval_modules = (
|
||||
is_toplevel_call: true,
|
||||
|
||||
window: globalThis.app_window,
|
||||
|
||||
storage,
|
||||
}
|
||||
|
||||
const result = run(module_fns, cxt, io_trace)
|
||||
|
||||
@@ -80,6 +80,8 @@ const add_trivial_definition = node => {
|
||||
* will be assigned by the time the closures would be called
|
||||
*/
|
||||
|
||||
const DEFAULT_GLOBALS = new Set(['leporello']) // Leporello.js API
|
||||
|
||||
export const find_definitions = (ast, globals, scope = {}, closure_scope = {}, module_name) => {
|
||||
|
||||
// sanity check
|
||||
@@ -94,7 +96,7 @@ export const find_definitions = (ast, globals, scope = {}, closure_scope = {}, m
|
||||
} else {
|
||||
const definition = scope[ast.value]
|
||||
if(definition == null){
|
||||
if(globals.has(ast.value)) {
|
||||
if(globals.has(ast.value) || DEFAULT_GLOBALS.has(ast.value)) {
|
||||
return {node: {...ast, definition: 'global'}, undeclared: null, closed: new Set()}
|
||||
} else {
|
||||
return {node: ast, undeclared: [ast], closed: new Set()}
|
||||
|
||||
17
src/index.js
17
src/index.js
@@ -52,7 +52,7 @@ const get_html_url = state => {
|
||||
const on_window_load = w => {
|
||||
init_window_service_worker(w)
|
||||
exec(
|
||||
'open_app_window',
|
||||
'run_code',
|
||||
new Set(Object.getOwnPropertyNames(w))
|
||||
)
|
||||
}
|
||||
@@ -78,6 +78,7 @@ const open_run_iframe = (state) => {
|
||||
export const open_app_window = state => {
|
||||
// TODO set_error_handler? Or we dont need to set_error_handler for child
|
||||
// window because error is always caught by parent window handler?
|
||||
exec('open_app_window')
|
||||
globalThis.app_window.close()
|
||||
globalThis.app_window = open(get_html_url(state))
|
||||
init_app_window(globalThis.app_window)
|
||||
@@ -139,10 +140,8 @@ const init_app_window = w => {
|
||||
add_load_handler()
|
||||
}
|
||||
|
||||
export const reload_app_window = state => {
|
||||
// TODO after window location reload, open_app_window command will be fired.
|
||||
// Maybe we should have separate commands for open_app_window and
|
||||
// reload_app_window?
|
||||
const reload_app_window = state => {
|
||||
// after window location reload, `run_code` command will be fired.
|
||||
globalThis.app_window.location = get_html_url(state)
|
||||
}
|
||||
|
||||
@@ -154,19 +153,23 @@ const get_entrypoint_settings = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const exec_and_reload_app_window = (...exec_args) => {
|
||||
exec(...exec_args)
|
||||
reload_app_window(get_state())
|
||||
}
|
||||
|
||||
export const open_directory = () => {
|
||||
if(globalThis.showDirectoryPicker == null) {
|
||||
throw new Error('Your browser is not supporting File System Access API')
|
||||
}
|
||||
open_dir(true).then(dir => {
|
||||
exec('load_dir', dir, true, get_entrypoint_settings())
|
||||
exec_and_reload_app_window('load_dir', dir, true, get_entrypoint_settings())
|
||||
})
|
||||
}
|
||||
|
||||
export const close_directory = async () => {
|
||||
close_dir()
|
||||
exec('load_dir', await examples_dir_promise, false, get_entrypoint_settings())
|
||||
exec_and_reload_app_window('load_dir', await examples_dir_promise, false, get_entrypoint_settings())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,18 +76,25 @@ const make_patched_method = (window, original, name, use_context) => {
|
||||
: original.apply(this, args)
|
||||
|
||||
if(value?.[Symbol.toStringTag] == 'Promise') {
|
||||
// TODO use __original_then, not finally which calls
|
||||
// patched 'then'?
|
||||
value = value.finally(() => {
|
||||
if(cxt_copy != cxt) {
|
||||
return
|
||||
}
|
||||
if(cxt.io_trace_is_replay_aborted) {
|
||||
// Non necessary
|
||||
return
|
||||
}
|
||||
cxt.io_trace.push({type: 'resolution', index})
|
||||
})
|
||||
value = value
|
||||
.then(val => {
|
||||
value.status = {ok: true, value: val}
|
||||
return val
|
||||
})
|
||||
.catch(error => {
|
||||
value.status = {ok: true, error}
|
||||
throw error
|
||||
})
|
||||
.finally(() => {
|
||||
if(cxt_copy != cxt) {
|
||||
return
|
||||
}
|
||||
if(cxt.io_trace_is_replay_aborted) {
|
||||
// Non necessary
|
||||
return
|
||||
}
|
||||
cxt.io_trace.push({type: 'resolution', index})
|
||||
})
|
||||
}
|
||||
|
||||
ok = true
|
||||
@@ -150,10 +157,11 @@ const make_patched_method = (window, original, name, use_context) => {
|
||||
)
|
||||
|
||||
if(next_resolution != null && !cxt.io_trace_resolver_is_set) {
|
||||
const original_setTimeout = cxt.window.setTimeout.__original
|
||||
cxt.io_trace_resolver_is_set = true
|
||||
|
||||
original_setTimeout(() => {
|
||||
// use setTimeout function from host window (because this module was
|
||||
// loaded as `external` by host window)
|
||||
setTimeout(() => {
|
||||
if(cxt_copy != cxt) {
|
||||
return
|
||||
}
|
||||
@@ -180,14 +188,22 @@ const make_patched_method = (window, original, name, use_context) => {
|
||||
cxt.io_trace[cxt.io_trace_index].type == 'resolution'
|
||||
) {
|
||||
const resolution = cxt.io_trace[cxt.io_trace_index]
|
||||
const resolver = cxt.io_trace_resolvers.get(resolution.index)
|
||||
const {resolve, reject} = cxt.io_trace_resolvers.get(resolution.index)
|
||||
|
||||
cxt.io_trace_index++
|
||||
|
||||
if(cxt.io_trace[resolution.index].name == 'setTimeout') {
|
||||
resolver()
|
||||
resolve()
|
||||
} else {
|
||||
resolver(cxt.io_trace[resolution.index].value)
|
||||
const promise = cxt.io_trace[resolution.index].value
|
||||
if(promise.status == null) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
if(promise.status.ok) {
|
||||
resolve(promise.status.value)
|
||||
} else {
|
||||
reject(promise.status.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,12 +219,12 @@ const make_patched_method = (window, original, name, use_context) => {
|
||||
// trace) and instanceof would not work
|
||||
if(call.value?.[Symbol.toStringTag] == 'Promise') {
|
||||
// Always make promise originate from app_window
|
||||
return new cxt.window.Promise(resolve => {
|
||||
cxt.io_trace_resolvers.set(cxt.io_trace_index - 1, resolve)
|
||||
return new cxt.window.Promise((resolve, reject) => {
|
||||
cxt.io_trace_resolvers.set(cxt.io_trace_index - 1, {resolve, reject})
|
||||
})
|
||||
} else if(name == 'setTimeout') {
|
||||
const timeout_cb = args[0]
|
||||
cxt.io_trace_resolvers.set(cxt.io_trace_index - 1, timeout_cb)
|
||||
cxt.io_trace_resolvers.set(cxt.io_trace_index - 1, {resolve: timeout_cb})
|
||||
return call.value
|
||||
} else {
|
||||
return call.value
|
||||
|
||||
@@ -19,9 +19,8 @@ const gen_to_promise = gen_fn => {
|
||||
if(result.done){
|
||||
return result.value
|
||||
} else {
|
||||
// If promise
|
||||
if(result.value?.then != null) {
|
||||
return result.value.__original_then(
|
||||
if(result.value?.[Symbol.toStringTag] == 'Promise') {
|
||||
return result.value.then(
|
||||
value => next(gen.next(value)),
|
||||
error => next(gen.throw(error)),
|
||||
)
|
||||
@@ -36,7 +35,7 @@ const gen_to_promise = gen_fn => {
|
||||
|
||||
const make_promise_with_rejector = cxt => {
|
||||
let rejector
|
||||
const p = new cxt.window.Promise(r => rejector = r)
|
||||
const p = new Promise(r => rejector = r)
|
||||
return [p, rejector]
|
||||
}
|
||||
|
||||
@@ -107,8 +106,8 @@ const do_run = function*(module_fns, cxt, io_trace){
|
||||
create_array,
|
||||
create_object,
|
||||
)
|
||||
if(result instanceof cxt.window.Promise) {
|
||||
yield cxt.window.Promise.race([replay_aborted_promise, result])
|
||||
if(result?.[Symbol.toStringTag] == 'Promise') {
|
||||
yield Promise.race([replay_aborted_promise, result])
|
||||
} else {
|
||||
yield result
|
||||
}
|
||||
@@ -144,7 +143,10 @@ export const run = gen_to_promise(function*(module_fns, cxt, io_trace) {
|
||||
if(!cxt.window.__is_initialized) {
|
||||
defineMultiversion(cxt.window)
|
||||
apply_io_patches(cxt.window)
|
||||
inject_leporello_api(cxt)
|
||||
cxt.window.__is_initialized = true
|
||||
} else {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
const result = yield* do_run(module_fns, cxt, io_trace)
|
||||
@@ -154,14 +156,21 @@ export const run = gen_to_promise(function*(module_fns, cxt, io_trace) {
|
||||
result.rt_cxt.is_recording_deferred_calls = false
|
||||
|
||||
// run again without io trace
|
||||
// TODO reload app_window before second run
|
||||
return yield* do_run(module_fns, cxt, null)
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
const inject_leporello_api = cxt => {
|
||||
cxt.window.leporello = { storage: cxt.storage }
|
||||
}
|
||||
|
||||
const apply_promise_patch = cxt => {
|
||||
if(cxt.window.Promise.prototype.__original_then != null) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
const original_then = cxt.window.Promise.prototype.then
|
||||
cxt.window.Promise.prototype.__original_then = cxt.window.Promise.prototype.then
|
||||
|
||||
@@ -197,6 +206,7 @@ const apply_promise_patch = cxt => {
|
||||
|
||||
const remove_promise_patch = cxt => {
|
||||
cxt.window.Promise.prototype.then = cxt.window.Promise.prototype.__original_then
|
||||
delete cxt.window.Promise.prototype.__original_then
|
||||
}
|
||||
|
||||
export const set_record_call = cxt => {
|
||||
@@ -339,7 +349,7 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure, has_versione
|
||||
try {
|
||||
value = fn(...args)
|
||||
ok = true
|
||||
if(value instanceof cxt.window.Promise) {
|
||||
if(value?.[Symbol.toStringTag] == 'Promise') {
|
||||
set_record_call(cxt)
|
||||
}
|
||||
return value
|
||||
@@ -485,7 +495,7 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
|
||||
value = undefined
|
||||
}
|
||||
ok = true
|
||||
if(value instanceof cxt.window.Promise) {
|
||||
if(value?.[Symbol.toStringTag] == 'Promise') {
|
||||
set_record_call(cxt)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ export const set_push = (x,y) => new Set([...x, y])
|
||||
|
||||
export const set_union = (x,y) => new Set([...x, ...y])
|
||||
|
||||
export const set_is_eq = (a, b) =>
|
||||
a.size === b.size && [...a].every(value => b.has(value))
|
||||
|
||||
export const set_diff = (x,y) => {
|
||||
return new Set([...x].filter(el => !y.has(el)))
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ const isError = object =>
|
||||
||
|
||||
object instanceof globalThis.app_window.Error
|
||||
|
||||
const isPromise = object =>
|
||||
object instanceof globalThis.app_window.Promise
|
||||
const isPromise = object => object?.[Symbol.toStringTag] == 'Promise'
|
||||
|
||||
// Override behaviour for Date, becase Date has toJSON defined
|
||||
const isDate = object =>
|
||||
|
||||
Reference in New Issue
Block a user