Skip to content

Commit 870f78f

Browse files
committed
fix #589; fix globalMetadata unloading; other minor things
1 parent 9bae1fd commit 870f78f

File tree

19 files changed

+285
-247
lines changed

19 files changed

+285
-247
lines changed

automation/build/esbuild.dev.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ const context = await esbuild.context({
3030
format: 'cjs',
3131
target: 'es2022',
3232
logLevel: 'info',
33-
sourcemap: 'inline',
33+
sourcemap: false,
34+
// NOTE: disabled because some stupid invalid sourcemap warnings
35+
// sourcemap: 'inline',
3436
treeShaking: true,
3537
outdir: `exampleVault/.obsidian/plugins/${manifest.id}/`,
3638
outbase: 'packages/obsidian/src',

bun.lock

Lines changed: 71 additions & 68 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
"@types/bun": "^1.2.18",
3636
"builtin-modules": "^5.0.0",
3737
"elysia": "^1.3.5",
38-
"esbuild": "^0.25.5",
38+
"esbuild": "^0.25.6",
3939
"esbuild-plugin-copy-watch": "^2.3.1",
4040
"esbuild-svelte": "^0.9.3",
41-
"eslint": "^9.30.1",
41+
"eslint": "^9.31.0",
4242
"eslint-plugin-import": "^2.32.0",
4343
"eslint-plugin-isaacscript": "^4.0.0",
4444
"eslint-plugin-no-relative-import-paths": "^1.6.1",
@@ -51,7 +51,7 @@
5151
"svelte-check": "^4.2.2",
5252
"tslib": "^2.8.1",
5353
"typescript": "^5.8.3",
54-
"typescript-eslint": "^8.35.1",
54+
"typescript-eslint": "^8.36.0",
5555
"yaml": "^2.8.0"
5656
},
5757
"dependencies": {
@@ -60,9 +60,8 @@
6060
"itertools-ts": "^2.2.0",
6161
"mathjs": "^14.5.3",
6262
"moment": "^2.30.1",
63-
"svelte": "^5.35.2",
64-
"zod": "^3.25.75",
65-
"zod-validation-error": "^3.5.2"
63+
"svelte": "^5.35.6",
64+
"zod": "^4.0.5"
6665
},
6766
"private": true,
6867
"trustedDependencies": [

packages/core/src/api/API.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { ErrorLevel, MetaBindInternalError } from 'packages/core/src/utils/error
5151
import { parsePropPath } from 'packages/core/src/utils/prop/PropParser';
5252
import { Signal } from 'packages/core/src/utils/Signal';
5353
import { expectType, getUUID } from 'packages/core/src/utils/Utils';
54-
import { validateAPIArgs } from 'packages/core/src/utils/ZodUtils';
54+
import { validateAPIArgs, zodFunction } from 'packages/core/src/utils/ZodUtils';
5555
import { z } from 'zod';
5656
import type { MB_Comps, MetaBind } from '..';
5757

@@ -713,7 +713,7 @@ export abstract class API<Components extends MB_Comps> {
713713
validateAPIArgs(
714714
z.object({
715715
bindTarget: V_BindTargetDeclaration,
716-
updateFn: z.function().args(z.any()).returns(z.any()),
716+
updateFn: zodFunction<(value: unknown) => unknown>(),
717717
}),
718718
{
719719
bindTarget: bindTarget,
@@ -749,7 +749,7 @@ export abstract class API<Components extends MB_Comps> {
749749
z.object({
750750
bindTarget: V_BindTargetDeclaration,
751751
lifecycleHook: this.mb.internal.getLifecycleHookValidator(),
752-
callback: z.function().args(z.any()).returns(z.void()),
752+
callback: zodFunction<(value: unknown) => void>(),
753753
}),
754754
{
755755
bindTarget: bindTarget,

packages/core/src/config/validators/ButtonConfigValidators.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,54 @@ import { oneOf, schemaForType } from 'packages/core/src/utils/ZodUtils';
2121
import { z } from 'zod';
2222

2323
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
24-
function actionFieldNumber(action: string, name: string, description: string) {
24+
function actionFieldNumber(action: string, field: string, description: string) {
2525
return z.number({
26-
required_error: `The ${action} action requires a specified ${description} with the '${name}' field.`,
27-
invalid_type_error: `The ${action} action requires the value of the '${name}' fields to be a number.`,
26+
error: issue => {
27+
if (issue.input === undefined) {
28+
return `The ${action} action requires a specified ${description} with the '${field}' field.`;
29+
} else {
30+
return `The ${action} action requires the value of the '${field}' fields to be a number, but got ${typeof issue.input}.`;
31+
}
32+
},
2833
});
2934
}
3035

3136
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
32-
function actionFieldString(action: string, name: string, description: string) {
37+
function actionFieldString(action: string, field: string, description: string) {
3338
return z.string({
34-
required_error: `The ${action} action requires a specified ${description} with the '${name}' field.`,
39+
error: issue => {
40+
if (issue.input === undefined) {
41+
return `The ${action} action requires a specified ${description} with the '${field}' field.`;
42+
} else {
43+
return `The ${action} action requires the value of the '${field}' fields to be a string, but got ${typeof issue.input}.`;
44+
}
45+
},
3546
});
3647
}
3748

3849
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39-
function actionFieldCoerceString(action: string, name: string, description: string) {
50+
function actionFieldCoerceString(action: string, field: string, description: string) {
4051
return z.coerce.string({
41-
required_error: `The ${action} action requires a specified ${description} with the '${name}' field.`,
42-
invalid_type_error: `The ${action} action requires the value of the '${name}' fields to be a string.`,
52+
error: issue => {
53+
if (issue.input === undefined) {
54+
return `The ${action} action requires a specified ${description} with the '${field}' field.`;
55+
} else {
56+
return `The ${action} action requires the value of the '${field}' fields to be a string, but got ${typeof issue.input}.`;
57+
}
58+
},
4359
});
4460
}
4561

4662
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
47-
function actionFieldBool(action: string, name: string, description: string) {
63+
function actionFieldBool(action: string, field: string, description: string) {
4864
return z.boolean({
49-
required_error: `The ${action} action requires a specified ${description} with the '${name}' field.`,
50-
invalid_type_error: `The ${action} action requires the value of the '${name}' fields to be a boolean.`,
65+
error: issue => {
66+
if (issue.input === undefined) {
67+
return `The ${action} action requires a specified ${description} with the '${field}' field.`;
68+
} else {
69+
return `The ${action} action requires the value of the '${field}' fields to be a boolean, but got ${typeof issue.input}.`;
70+
}
71+
},
5172
});
5273
}
5374

@@ -62,7 +83,7 @@ export const V_JSButtonAction = schemaForType<JSButtonAction>()(
6283
z.object({
6384
type: z.literal(ButtonActionType.JS),
6485
file: actionFieldString('js', 'file', 'file path to the file to run'),
65-
args: z.record(z.unknown()).optional(),
86+
args: z.record(z.string(), z.unknown()).optional(),
6687
}),
6788
);
6889

@@ -120,10 +141,7 @@ export const V_UpdateMetadataButtonAction = schemaForType<UpdateMetadataButtonAc
120141
'evaluate',
121142
'value for whether to evaluate the value as a JavaScript expression',
122143
),
123-
value: z.coerce.string({
124-
required_error: `The updateMetadata action requires a specified value for the update with the 'value' field.`,
125-
invalid_type_error: `The updateMetadata action requires the value of the 'value' fields to be a string.`,
126-
}),
144+
value: actionFieldCoerceString('updateMetadata', 'value for the update', 'value'),
127145
}),
128146
);
129147

@@ -185,7 +203,7 @@ export const V_InlineJSButtonAction = schemaForType<InlineJSButtonAction>()(
185203
z.object({
186204
type: z.literal(ButtonActionType.INLINE_JS),
187205
code: actionFieldString('inlineJS', 'code', 'code string to run'),
188-
args: z.record(z.unknown()).optional(),
206+
args: z.record(z.string(), z.unknown()).optional(),
189207
}),
190208
);
191209

@@ -225,5 +243,5 @@ export const V_ButtonConfig = schemaForType<ButtonConfig>()(
225243
action: V_ButtonAction.optional(),
226244
actions: V_ButtonAction.array().optional(),
227245
})
228-
.superRefine(oneOf('action', 'actions')),
246+
.check(oneOf('action', 'actions')),
229247
);

packages/core/src/config/validators/Validators.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { Mountable } from 'packages/core/src/utils/Mountable';
3737
import { PropAccessType } from 'packages/core/src/utils/prop/PropAccess';
3838
import { PropPath } from 'packages/core/src/utils/prop/PropPath';
3939
import { Signal } from 'packages/core/src/utils/Signal';
40-
import { schemaForType } from 'packages/core/src/utils/ZodUtils';
40+
import { schemaForType, zodFunction } from 'packages/core/src/utils/ZodUtils';
4141
import { z } from 'zod';
4242

4343
export const V_FilePath = schemaForType<string>()(z.string());
@@ -54,7 +54,7 @@ export const V_BindTargetScope = schemaForType<BindTargetScope>()(z.instanceof(B
5454

5555
export const V_Signal = schemaForType<Signal<unknown>>()(z.instanceof(Signal));
5656

57-
export const V_VoidFunction = schemaForType<() => void>()(z.function().args().returns(z.void()));
57+
export const V_VoidFunction = schemaForType<() => void>()(zodFunction<() => void>());
5858

5959
export const V_FieldMountable = schemaForType<FieldMountable>()(z.instanceof(FieldMountable));
6060
export const V_Mountable = schemaForType<Mountable>()(z.instanceof(Mountable));

packages/core/src/metadata/InternalMetadataSources.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export class InternalMetadataSource extends FilePathMetadataSource<FilePathMetad
2828
};
2929
}
3030

31-
public syncExternal(_cacheItem: FilePathMetadataCacheItem): void {
32-
// Do nothing
31+
public syncExternal(_cacheItem: FilePathMetadataCacheItem): Promise<void> {
32+
return Promise.resolve();
3333
}
3434
}
3535

@@ -55,8 +55,9 @@ export class TestMetadataSource extends FilePathMetadataSource<FilePathMetadataC
5555
};
5656
}
5757

58-
public syncExternal(cacheItem: FilePathMetadataCacheItem): void {
58+
public syncExternal(cacheItem: FilePathMetadataCacheItem): Promise<void> {
5959
this.externalMetadata[cacheItem.storagePath] = cacheItem.data;
60+
return Promise.resolve();
6061
}
6162
}
6263

@@ -102,11 +103,11 @@ export class GlobalMetadataSource implements IMetadataSource<GlobalMetadataCache
102103
}
103104

104105
public getCacheItemForStoragePath(_storagePath: string): GlobalMetadataCacheItem | undefined {
105-
return this.cache;
106+
return undefined;
106107
}
107108

108-
public iterateCacheItems(): IterableIterator<GlobalMetadataCacheItem> {
109-
return [this.cache][Symbol.iterator]();
109+
public getCacheItems(): GlobalMetadataCacheItem[] {
110+
return [this.cache];
110111
}
111112

112113
public onCycle(_cacheItem: GlobalMetadataCacheItem): void {
@@ -131,8 +132,8 @@ export class GlobalMetadataSource implements IMetadataSource<GlobalMetadataCache
131132
return this.cache;
132133
}
133134

134-
public syncExternal(_cacheItem: GlobalMetadataCacheItem): void {
135-
// noop
135+
public syncExternal(_cacheItem: GlobalMetadataCacheItem): Promise<void> {
136+
return Promise.resolve();
136137
}
137138

138139
public unsubscribe(subscription: IMetadataSubscription): GlobalMetadataCacheItem {
@@ -199,8 +200,8 @@ export class ScopeMetadataSource implements IMetadataSource<IMetadataCacheItem>
199200
return undefined;
200201
}
201202

202-
public iterateCacheItems(): IterableIterator<IMetadataCacheItem> {
203-
return [][Symbol.iterator]();
203+
public getCacheItems(): IMetadataCacheItem[] {
204+
return [];
204205
}
205206

206207
public onCycle(_cacheItem: IMetadataCacheItem): void {
@@ -227,8 +228,8 @@ export class ScopeMetadataSource implements IMetadataSource<IMetadataCacheItem>
227228
});
228229
}
229230

230-
public syncExternal(_cacheItem: IMetadataCacheItem): void {
231-
// noop
231+
public syncExternal(_cacheItem: IMetadataCacheItem): Promise<void> {
232+
return Promise.resolve();
232233
}
233234

234235
public unsubscribe(_subscription: IMetadataSubscription): IMetadataCacheItem {

packages/core/src/metadata/MetadataManager.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -354,21 +354,31 @@ export class MetadataManager {
354354
/**
355355
* Internal update function that runs each cycle.
356356
*/
357-
public cycle(): void {
358-
for (const source of this.sources.values()) {
359-
const markedForDelete: IMetadataCacheItem[] = [];
357+
public async cycle(): Promise<void> {
358+
const results = await Promise.allSettled(this.sources.values().map(source => this.cycleSource(source)));
359+
360+
for (const result of results) {
361+
if (result.status === 'rejected') {
362+
console.warn(`meta-bind | MetadataManager >> failed to cycle source`, result.reason);
363+
}
364+
}
365+
}
366+
367+
private async cycleSource(source: MetadataSource): Promise<void> {
368+
const markedForDelete: IMetadataCacheItem[] = [];
360369

361-
for (const cacheItem of source.iterateCacheItems()) {
370+
const results = await Promise.allSettled(
371+
source.getCacheItems().map(async cacheItem => {
362372
source.onCycle(cacheItem);
363373

364374
// if the cache is dirty, sync the changes to the external source
365375
if (cacheItem.dirty) {
366376
try {
367-
source.syncExternal(cacheItem);
377+
await source.syncExternal(cacheItem);
378+
cacheItem.dirty = false;
368379
} catch (e) {
369380
console.warn(`failed to sync changes to external source for ${source.id}`, e);
370381
}
371-
cacheItem.dirty = false;
372382
}
373383
// decrease the external write lock duration
374384
if (cacheItem.externalWriteLock > 0) {
@@ -386,10 +396,15 @@ export class MetadataManager {
386396
) {
387397
markedForDelete.push(cacheItem);
388398
}
389-
}
399+
}),
400+
);
390401

391-
for (const cacheItem of markedForDelete) {
392-
source.deleteCache(cacheItem);
402+
for (const result of results) {
403+
if (result.status === 'rejected') {
404+
console.warn(
405+
`meta-bind | MetadataManager >> failed to cycle cache item in source ${source.id}`,
406+
result.reason,
407+
);
393408
}
394409
}
395410
}

packages/core/src/metadata/MetadataSource.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ export interface IMetadataSource<T extends IMetadataCacheItem> {
7070
onCycle(cacheItem: T): void;
7171

7272
/**
73-
* Iterates over all cache items.
73+
* Get all cache items as an array, so that it can e.g. be iterated over.
7474
*/
75-
iterateCacheItems(): IterableIterator<T>;
75+
getCacheItems(): T[];
7676

7777
/**
7878
* Can be used to stop the deletion of a cache item.
@@ -93,7 +93,7 @@ export interface IMetadataSource<T extends IMetadataCacheItem> {
9393
*
9494
* @param cacheItem
9595
*/
96-
syncExternal(cacheItem: T): void;
96+
syncExternal(cacheItem: T): Promise<void>;
9797

9898
/**
9999
* Updates the cache item with the given value.
@@ -232,8 +232,8 @@ export abstract class FilePathMetadataSource<T extends FilePathMetadataCacheItem
232232
// noop
233233
}
234234

235-
iterateCacheItems(): IterableIterator<T> {
236-
return this.cache.values();
235+
getCacheItems(): T[] {
236+
return this.cache.values().toArray();
237237
}
238238

239239
shouldDelete(_cacheItem: T): boolean {
@@ -244,7 +244,7 @@ export abstract class FilePathMetadataSource<T extends FilePathMetadataCacheItem
244244
this.cache.delete(cacheItem.storagePath);
245245
}
246246

247-
abstract syncExternal(cacheItem: T): void;
247+
abstract syncExternal(cacheItem: T): Promise<void>;
248248

249249
writeCache(value: unknown, bindTarget: BindTargetDeclaration): T {
250250
const cacheItem = this.getOrCreateCacheItem(bindTarget.storagePath);

packages/core/src/parsers/ButtonParser.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils';
22
import { P } from '@lemons_dev/parsinom/lib/ParsiNOM';
33
import type { ButtonConfig } from 'packages/core/src/config/ButtonConfig';
44
import { V_ButtonConfig } from 'packages/core/src/config/validators/ButtonConfigValidators';
5+
import type { MetaBind } from 'packages/core/src/index';
56
import { runParser } from 'packages/core/src/parsers/ParsingError';
67
import { DocsUtils } from 'packages/core/src/utils/DocsUtils';
78
import { ErrorCollection } from 'packages/core/src/utils/errors/ErrorCollection';
89
import { ErrorLevel, MetaBindButtonError } from 'packages/core/src/utils/errors/MetaBindErrors';
9-
import { validate } from 'packages/core/src/utils/ZodUtils';
10-
import { fromZodError } from 'zod-validation-error';
11-
import type { MetaBind } from '..';
10+
import { toReadableError, validate } from 'packages/core/src/utils/ZodUtils';
1211

1312
const P_ButtonGroupDeclaration = P.sequenceMap(
1413
(_, b) => b,
@@ -85,18 +84,13 @@ export class ButtonParser {
8584
const parsedConfig = validate(V_ButtonConfig, config);
8685

8786
if (!parsedConfig.success) {
88-
const niceError = fromZodError(parsedConfig.error, {
89-
unionSeparator: '\nOR ',
90-
issueSeparator: ' AND ',
91-
prefix: null,
92-
includePath: false,
93-
});
87+
const niceError = toReadableError(parsedConfig.error);
9488

9589
throw new MetaBindButtonError({
9690
errorLevel: ErrorLevel.ERROR,
9791
effect: 'The validation for the button config failed.',
9892
cause: 'Your button syntax seems to be invalid. Check that your button config follows what is described in the docs.',
99-
positionContext: niceError.message,
93+
positionContext: niceError,
10094
docs: [DocsUtils.linkToButtonConfig()],
10195
});
10296
}

0 commit comments

Comments
 (0)