Skip to content

Commit

Permalink
Fixes & optimizations
Browse files Browse the repository at this point in the history
Optimization
* Prefer isAsyncIterator over isAsyncIterable to reuse existing iterators (note: can be fooled by synchronous iterators)
* Show stack in debug logging
* export TagLoader type
* assign initial iterable value in a single Object.assign() in preference to walking down the hierarchy and doing it field by field.

Fixes:
* Correct test for a queue value in internalDebounceQueueIteratableIterator
* Don't test path when an initial (top level) iterable value push occurs. This (incorrectly) supressed initial values
* Assign in the (error) case of an iterable field being re-defined by an extended tag
  • Loading branch information
MatAtBread committed Jul 2, 2024
1 parent d98728f commit 318fc29
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 105 deletions.
36 changes: 22 additions & 14 deletions module/dist/ai-ui.cjs

Large diffs are not rendered by default.

36 changes: 22 additions & 14 deletions module/dist/ai-ui.js

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions module/dist/ai-ui.min.cjs

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions module/dist/ai-ui.min.js

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions module/dist/ai-ui.min.mjs

Large diffs are not rendered by default.

36 changes: 22 additions & 14 deletions module/dist/ai-ui.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion module/esm/ai-ui.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface PoElementMethods {
export interface CreateElement {
createElement(name: TagCreatorFunction<Element> | Node | keyof HTMLElementTagNameMap, attrs: any, ...children: ChildTags[]): Node;
}
interface TagLoader {
export interface TagLoader {
nodes(...c: ChildTags[]): (Node | ((Element & PoElementMethods)))[];
UniqueID: typeof UniqueID;
<Tags extends keyof HTMLElementTagNameMap>(): {
Expand Down
16 changes: 12 additions & 4 deletions module/esm/ai-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,11 @@ export const tag = function (_1, _2, _3) {
}
deepDefine(e, tagDefinition.declare, true);
deepDefine(e, tagDefinition.override);
const reAssign = new Set();
tagDefinition.iterable && Object.keys(tagDefinition.iterable).forEach(k => {
if (k in e) {
console.log(`Ignoring attempt to re-define iterable property "${k}" as it could already have consumers`);
reAssign.add(k);
}
else {
defineIterableProperty(e, k, tagDefinition.iterable[k]);
Expand All @@ -501,19 +503,25 @@ export const tag = function (_1, _2, _3) {
// Once the full tree of augmented DOM elements has been constructed, fire all the iterable propeerties
// so the full hierarchy gets to consume the initial state, unless they have been assigned
// by assignProps from a future
const combinedInitialIterableValues = {};
let hasInitialValues = false;
for (const base of newCallStack) {
if (base.iterable)
for (const k of Object.keys(base.iterable)) {
// We don't self-assign iterables that have themselves been assigned with futures
if (!(!noAttrs && k in attrs && (!isPromiseLike(attrs[k]) || !isAsyncIter(attrs[k])))) {
const value = e[k];
if (value?.valueOf() !== undefined) {
const attrExists = !noAttrs && k in attrs;
if ((reAssign.has(k) && attrExists) || !(attrExists && (!isPromiseLike(attrs[k]) || !isAsyncIter(attrs[k])))) {
const value = e[k]?.valueOf();
if (value !== undefined) {
// @ts-ignore - some props of e (HTMLElement) are read-only, and we don't know if k is one of them.
e[k] = value;
combinedInitialIterableValues[k] = value;
hasInitialValues = true;
}
}
}
}
if (hasInitialValues)
Object.assign(e, combinedInitialIterableValues);
}
return e;
};
Expand Down
4 changes: 2 additions & 2 deletions module/esm/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export const timeOutWarn = 5000;
const _console = {
log(...args) {
if (DEBUG)
console.log('(AI-UI) LOG:', ...args);
console.log('(AI-UI) LOG:', ...args, new Error().stack?.replace(/Error\n\s*.*\n/, '\n'));
},
warn(...args) {
if (DEBUG)
console.warn('(AI-UI) WARN:', ...args);
console.warn('(AI-UI) WARN:', ...args, new Error().stack?.replace(/Error\n\s*.*\n/, '\n'));
},
info(...args) {
if (DEBUG)
Expand Down
16 changes: 8 additions & 8 deletions module/esm/iterators.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export function isAsyncIter(o) {
return isAsyncIterable(o) || isAsyncIterator(o);
}
export function asyncIterator(o) {
if (isAsyncIterable(o))
return o[Symbol.asyncIterator]();
if (isAsyncIterator(o))
return o;
throw new Error("Not as async provider");
if (isAsyncIterable(o))
return o[Symbol.asyncIterator]();
throw new Error("Not an async provider");
}
export const asyncExtras = {
filterMap(fn, initialValue = Ignore) {
Expand Down Expand Up @@ -45,8 +45,8 @@ function assignHidden(d, s) {
}
return d;
}
const _pending = Symbol('_pending');
const _items = Symbol('_items');
const _pending = Symbol('pending');
const _items = Symbol('items');
function internalQueueIteratableIterator(stop = () => { }) {
const q = {
[_pending]: [],
Expand Down Expand Up @@ -137,7 +137,7 @@ function internalDebounceQueueIteratableIterator(stop = () => { }) {
if (!q[_items]) {
console.log('Discarding queue push as there are no consumers');
}
else if (!q[_items].find(v => v === value)) {
else if (!q[_items].find(v => v.value === value)) {
q[_items].push({ done: false, value });
}
}
Expand Down Expand Up @@ -309,10 +309,10 @@ export function defineIterableProperty(obj, name, v) {
return withoutPath[key];
}
else {
withPath ?? (withPath = filterMap(pds, o => isProxiedAsyncIterator(o) ? o[_proxiedAsyncIterator] : { a: o, path: '' }));
withPath ?? (withPath = filterMap(pds, o => isProxiedAsyncIterator(o) ? o[_proxiedAsyncIterator] : { a: o, path: null }));
let ai = filterMap(withPath, (o, p) => {
const v = destructure(o.a, path);
return p !== v || o.path.startsWith(path) ? v : Ignore;
return p !== v || o.path === null || o.path.startsWith(path) ? v : Ignore;
}, Ignore, destructure(a, path));
return ai[key];
}
Expand Down
4 changes: 2 additions & 2 deletions module/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@
},
"type": "module",
"types": "./esm/ai-ui.d.ts",
"version": "0.14.4"
"version": "0.14.5"
}
18 changes: 13 additions & 5 deletions module/src/ai-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface CreateElement {
}

/* The interface that creates a set of TagCreators for the specified DOM tags */
interface TagLoader {
export interface TagLoader {
nodes(...c: ChildTags[]): (Node | (/*P &*/ (Element & PoElementMethods)))[];
UniqueID: typeof UniqueID

Expand Down Expand Up @@ -584,9 +584,11 @@ export const tag = <TagLoader>function <Tags extends string,
}
deepDefine(e, tagDefinition.declare, true);
deepDefine(e, tagDefinition.override);
const reAssign = new Set<string>();
tagDefinition.iterable && Object.keys(tagDefinition.iterable).forEach(k => {
if (k in e) {
console.log(`Ignoring attempt to re-define iterable property "${k}" as it could already have consumers`);
reAssign.add(k);
} else {
defineIterableProperty(e, k, tagDefinition.iterable![k as keyof typeof tagDefinition.iterable])
}
Expand All @@ -602,18 +604,24 @@ export const tag = <TagLoader>function <Tags extends string,
// Once the full tree of augmented DOM elements has been constructed, fire all the iterable propeerties
// so the full hierarchy gets to consume the initial state, unless they have been assigned
// by assignProps from a future
const combinedInitialIterableValues = {};
let hasInitialValues = false;
for (const base of newCallStack) {
if (base.iterable) for (const k of Object.keys(base.iterable)) {
// We don't self-assign iterables that have themselves been assigned with futures
if (!(!noAttrs && k in attrs && (!isPromiseLike(attrs[k]) || !isAsyncIter(attrs[k])))) {
const value = e[k as keyof typeof e];
if (value?.valueOf() !== undefined) {
const attrExists = !noAttrs && k in attrs;
if ((reAssign.has(k) && attrExists) || !(attrExists && (!isPromiseLike(attrs[k]) || !isAsyncIter(attrs[k])))) {
const value = e[k as keyof typeof e]?.valueOf();
if (value !== undefined) {
// @ts-ignore - some props of e (HTMLElement) are read-only, and we don't know if k is one of them.
e[k] = value;
combinedInitialIterableValues[k] = value;
hasInitialValues = true;
}
}
}
}
if (hasInitialValues)
Object.assign(e, combinedInitialIterableValues);
}
return e;
}
Expand Down
4 changes: 2 additions & 2 deletions module/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ export const timeOutWarn = 5000;

const _console = {
log(...args: any) {
if (DEBUG) console.log('(AI-UI) LOG:', ...args)
if (DEBUG) console.log('(AI-UI) LOG:', ...args, new Error().stack?.replace(/Error\n\s*.*\n/,'\n'))
},
warn(...args: any) {
if (DEBUG) console.warn('(AI-UI) WARN:', ...args)
if (DEBUG) console.warn('(AI-UI) WARN:', ...args, new Error().stack?.replace(/Error\n\s*.*\n/,'\n'))
},
info(...args: any) {
if (DEBUG) console.trace('(AI-UI) INFO:', ...args)
Expand Down
16 changes: 8 additions & 8 deletions module/src/iterators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export function isAsyncIter<T = unknown>(o: any | AsyncIterable<T> | AsyncIterat
export type AsyncProvider<T> = AsyncIterator<T> | AsyncIterable<T>

export function asyncIterator<T>(o: AsyncProvider<T>) {
if (isAsyncIterable(o)) return o[Symbol.asyncIterator]();
if (isAsyncIterator(o)) return o;
throw new Error("Not as async provider");
if (isAsyncIterable(o)) return o[Symbol.asyncIterator]();
throw new Error("Not an async provider");
}

type AsyncIterableHelpers = typeof asyncExtras;
Expand Down Expand Up @@ -130,8 +130,8 @@ function assignHidden<D extends {}, S extends {}>(d: D, s: S) {
return d as D & S;
}

const _pending = Symbol('_pending');
const _items = Symbol('_items');
const _pending = Symbol('pending');
const _items = Symbol('items');
function internalQueueIteratableIterator<T>(stop = () => { }) {
const q = {
[_pending]: [] as DeferredPromise<IteratorResult<T>>[] | null,
Expand Down Expand Up @@ -224,7 +224,7 @@ function internalDebounceQueueIteratableIterator<T>(stop = () => { }) {
} else {
if (!q[_items]) {
console.log('Discarding queue push as there are no consumers');
} else if (!q[_items].find(v => v === value)) {
} else if (!q[_items].find(v => v.value === value)) {
q[_items].push({ done: false, value });
}
}
Expand Down Expand Up @@ -378,7 +378,7 @@ export function defineIterableProperty<T extends {}, const N extends string | sy
throw new TypeError('Iterable properties cannot be of type "' + typeof a + '"');
}

type WithPath = { [_proxiedAsyncIterator]: { a: V, path: string }};
type WithPath = { [_proxiedAsyncIterator]: { a: V, path: string | null }};
type PossiblyWithPath = V | WithPath;
function isProxiedAsyncIterator(o: PossiblyWithPath): o is WithPath {
return isObjectLike(o) && _proxiedAsyncIterator in o;
Expand Down Expand Up @@ -424,11 +424,11 @@ export function defineIterableProperty<T extends {}, const N extends string | sy
withoutPath ??= filterMap(pds, o => isProxiedAsyncIterator(o) ? o[_proxiedAsyncIterator].a : o);
return withoutPath[key as keyof typeof pds];
} else {
withPath ??= filterMap(pds, o => isProxiedAsyncIterator(o) ? o[_proxiedAsyncIterator] : { a: o, path: '' });
withPath ??= filterMap(pds, o => isProxiedAsyncIterator(o) ? o[_proxiedAsyncIterator] : { a: o, path: null });

let ai = filterMap(withPath, (o, p) => {
const v = destructure(o.a, path);
return p !== v || o.path.startsWith(path) ? v : Ignore ;
return p !== v || o.path === null || o.path.startsWith(path) ? v : Ignore ;
}, Ignore, destructure(a, path));
return ai[key as keyof typeof ai];
}
Expand Down

0 comments on commit 318fc29

Please sign in to comment.