Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@logseq/libs

Package Overview
Dependencies
Maintainers
6
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@logseq/libs - npm Package Compare versions

Comparing version
0.2.1
to
0.2.2
+184
scripts/extract-sdk-schema.js
#!/usr/bin/env node
/**
* Extracts metadata about the Logseq JS SDK from the generated *.d.ts files.
*
* This script uses ts-morph so we can rely on the TypeScript compiler's view of
* the declarations. We intentionally read the emitted declaration files in
* dist/ so that consumers do not need to depend on the source layout.
*
* The resulting schema is written to dist/logseq-sdk-schema.json and contains
* a simplified representation that downstream tooling (Babashka) can consume.
*/
const fs = require('node:fs');
const path = require('node:path');
const { Project, Node } = require('ts-morph');
const ROOT = path.resolve(__dirname, '..');
const DIST_DIR = path.join(ROOT, 'dist');
const OUTPUT_FILE = path.join(DIST_DIR, 'logseq-sdk-schema.json');
const DECL_FILES = [
'LSPlugin.d.ts',
'LSPlugin.user.d.ts',
];
/**
* Interfaces whose methods will be turned into CLJS wrappers at runtime.
* These correspond to `logseq.<Namespace>` targets in the JS SDK.
*/
const TARGET_INTERFACES = [
'IAppProxy',
'IEditorProxy',
'IDBProxy',
'IUIProxy',
'IUtilsProxy',
'IGitProxy',
'IAssetsProxy',
];
/**
* Simple heuristics to determine whether a parameter should be converted via
* cljs-bean when crossing the JS <-> CLJS boundary.
*/
const BEAN_TO_JS_REGEX =
/(Record<|Array<|Partial<|UIOptions|UIContainerAttrs|StyleString|StyleOptions|object|any|unknown|IHookEvent|BlockEntity|PageEntity|Promise<\s*Record)/i;
const project = new Project({
compilerOptions: { allowJs: true },
});
DECL_FILES.forEach((file) => {
const full = path.join(DIST_DIR, file);
if (fs.existsSync(full)) {
project.addSourceFileAtPath(full);
}
});
const schema = {
generatedAt: new Date().toISOString(),
interfaces: {},
classes: {},
};
const serializeDoc = (symbol) => {
if (!symbol) return undefined;
const decl = symbol.getDeclarations()[0];
if (!decl) return undefined;
const docs = decl
.getJsDocs()
.map((doc) => doc.getComment())
.filter(Boolean);
return docs.length ? docs.join('\n\n') : undefined;
};
const serializeParameter = (signature, symbol, memberNode) => {
const name = symbol.getName();
const declaration = symbol.getDeclarations()[0];
let typeText;
let optional = symbol.isOptional?.() ?? false;
let rest = symbol.isRestParameter?.() ?? false;
if (declaration && Node.isParameterDeclaration(declaration)) {
typeText = declaration.getType().getText();
optional = declaration.hasQuestionToken?.() ?? false;
rest = declaration.isRestParameter?.() ?? false;
} else {
const location =
signature.getDeclaration?.() ??
memberNode ??
declaration ??
symbol.getDeclarations()[0];
typeText = symbol.getTypeAtLocation(location).getText();
}
const convertToJs = BEAN_TO_JS_REGEX.test(typeText);
return {
name,
type: typeText,
optional,
rest,
beanToJs: convertToJs,
};
};
const serializeSignature = (sig, memberNode) => {
const params = sig.getParameters().map((paramSymbol) =>
serializeParameter(sig, paramSymbol, memberNode)
);
const returnType = sig.getReturnType().getText();
return {
parameters: params,
returnType,
};
};
const serializeCallable = (symbol, member) => {
if (!symbol) return null;
const type = symbol.getTypeAtLocation(member);
const callSignatures = type.getCallSignatures();
if (!callSignatures.length) {
return null;
}
return {
name: symbol.getName(),
documentation: serializeDoc(symbol),
signatures: callSignatures.map((sig) => serializeSignature(sig, member)),
};
};
const sourceFiles = project.getSourceFiles();
sourceFiles.forEach((source) => {
source.getInterfaces().forEach((iface) => {
const name = iface.getName();
if (!TARGET_INTERFACES.includes(name)) {
return;
}
const interfaceSymbol = iface.getType().getSymbol();
const doc = serializeDoc(interfaceSymbol);
const methods = iface
.getMembers()
.map((member) => serializeCallable(member.getSymbol(), member))
.filter(Boolean);
schema.interfaces[name] = {
documentation: doc,
methods,
};
});
source.getClasses().forEach((cls) => {
const name = cls.getName();
if (name !== 'LSPluginUser') {
return;
}
const classSymbol = cls.getType().getSymbol();
const doc = serializeDoc(classSymbol);
const methods = cls
.getInstanceMethods()
.filter((method) => method.getName() !== 'constructor')
.map((method) => serializeCallable(method.getSymbol(), method))
.filter(Boolean);
const getters = cls.getGetAccessors().map((accessor) => ({
name: accessor.getName(),
documentation: serializeDoc(accessor.getSymbol()),
returnType: accessor.getReturnType().getText(),
}));
schema.classes[name] = {
documentation: doc,
methods,
getters,
};
});
});
fs.mkdirSync(DIST_DIR, { recursive: true });
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(schema, null, 2));
console.log(`Wrote ${OUTPUT_FILE}`);
+3
-0

@@ -49,2 +49,3 @@ import EventEmitter from 'eventemitter3';

version: string;
runtime: string;
mode: 'shadow' | 'iframe';

@@ -60,2 +61,3 @@ webPkg?: any;

version: string;
runtime: string;
[key: string]: any;

@@ -130,2 +132,3 @@ }

get isWebPlugin(): boolean;
get installedFromUserWebUrl(): any;
get layoutCore(): any;

@@ -132,0 +135,0 @@ get isInstalledInLocalDotRoot(): boolean;

+13
-10

@@ -160,2 +160,3 @@ import * as CSS from 'csstype';

title: string;
fullTitle: string;
content?: string;

@@ -165,5 +166,5 @@ page: IEntityID;

updatedAt: number;
ident?: string;
properties?: Record<string, any>;
'collapsed?': boolean;
left?: IEntityID;
anchor?: string;

@@ -238,3 +239,3 @@ body?: any;

export declare type ExternalCommandType = 'logseq.command/run' | 'logseq.editor/cycle-todo' | 'logseq.editor/down' | 'logseq.editor/up' | 'logseq.editor/expand-block-children' | 'logseq.editor/collapse-block-children' | 'logseq.editor/open-file-in-default-app' | 'logseq.editor/open-file-in-directory' | 'logseq.editor/select-all-blocks' | 'logseq.editor/toggle-open-blocks' | 'logseq.editor/zoom-in' | 'logseq.editor/zoom-out' | 'logseq.editor/indent' | 'logseq.editor/outdent' | 'logseq.editor/copy' | 'logseq.editor/cut' | 'logseq.go/home' | 'logseq.go/journals' | 'logseq.go/keyboard-shortcuts' | 'logseq.go/next-journal' | 'logseq.go/prev-journal' | 'logseq.go/search' | 'logseq.go/tomorrow' | 'logseq.go/backward' | 'logseq.go/forward' | 'logseq.search/re-index' | 'logseq.sidebar/clear' | 'logseq.sidebar/open-today-page' | 'logseq.ui/goto-plugins' | 'logseq.ui/select-theme-color' | 'logseq.ui/toggle-brackets' | 'logseq.ui/toggle-contents' | 'logseq.ui/toggle-document-mode' | 'logseq.ui/toggle-help' | 'logseq.ui/toggle-left-sidebar' | 'logseq.ui/toggle-right-sidebar' | 'logseq.ui/toggle-settings' | 'logseq.ui/toggle-theme' | 'logseq.ui/toggle-wide-mode';
export declare type UserProxyTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets';
export declare type UserProxyNSTags = 'app' | 'editor' | 'db' | 'git' | 'ui' | 'assets' | 'utils';
export declare type SearchIndiceInitStatus = boolean;

@@ -526,2 +527,3 @@ export declare type SearchBlockItem = {

newBlockUUID: () => Promise<string>;
isPageBlock: (block: BlockEntity | PageEntity) => Boolean;
/**

@@ -537,2 +539,4 @@ * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-reddit-hot-news

sibling: boolean;
start: boolean;
end: boolean;
isPageBlock: boolean;

@@ -604,10 +608,4 @@ focus: boolean;

getProperty: (key: string) => Promise<BlockEntity | null>;
/**
* insert or update property entity
* @param key
* @param schema
* @param opts
*/
upsertProperty: (key: string, schema?: Partial<{
type: 'default' | 'map' | 'number' | 'keyword' | 'node' | 'date' | 'checkbox' | string;
type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string;
cardinality: 'many' | 'one';

@@ -619,6 +617,8 @@ hide: boolean;

}) => Promise<IEntityID>;
removeProperty: (key: string) => Promise<void>;
upsertBlockProperty: (block: BlockIdentity, key: string, value: any) => Promise<void>;
removeBlockProperty: (block: BlockIdentity, key: string) => Promise<void>;
getBlockProperty: (block: BlockIdentity, key: string) => Promise<BlockEntity | string | null>;
getBlockProperty: (block: BlockIdentity, key: string) => Promise<BlockEntity | unknown>;
getBlockProperties: (block: BlockIdentity) => Promise<Record<string, any> | null>;
getPageProperties: (page: PageIdentity) => Promise<Record<string, any> | null>;
scrollToBlockInPage: (pageName: BlockPageName, blockId: BlockIdentity, opts?: {

@@ -708,2 +708,5 @@ replaceState: boolean;

}
export interface IUtilsProxy {
toJs: <R = unknown>(obj: {}) => Promise<R>;
}
/**

@@ -710,0 +713,0 @@ * Assets related APIs

import { PluginLogger } from './helpers';
import { LSPluginCaller } from './LSPlugin.caller';
import * as callableAPIs from './callable.apis';
import { IAppProxy, IDBProxy, IEditorProxy, ILSPluginUser, LSPluginBaseInfo, LSPluginUserEvents, StyleString, Theme, UIOptions, UIContainerAttrs, SettingSchemaDesc, IUserOffHook, IGitProxy, IUIProxy, UserProxyTags, IAssetsProxy } from './LSPlugin';
import { IAppProxy, IDBProxy, IEditorProxy, ILSPluginUser, LSPluginBaseInfo, LSPluginUserEvents, StyleString, Theme, UIOptions, UIContainerAttrs, SettingSchemaDesc, IUserOffHook, IGitProxy, IUIProxy, UserProxyNSTags, IAssetsProxy, IUtilsProxy } from './LSPlugin';
import * as CSS from 'csstype';

@@ -86,14 +86,12 @@ import EventEmitter from 'eventemitter3';

*/
_makeUserProxy(target: any, tag?: UserProxyTags): any;
_makeUserProxy(target: any, nstag?: UserProxyNSTags): any;
_execCallableAPIAsync(method: callableMethods, ...args: any[]): Promise<any>;
_execCallableAPI(method: callableMethods, ...args: any[]): void;
_callWin(...args: any[]): Promise<any>;
/**
* The interface methods of {@link IAppProxy}
*/
get App(): IAppProxy;
get Editor(): IEditorProxy;
get DB(): IDBProxy;
get UI(): IUIProxy;
get Utils(): IUtilsProxy;
get Git(): IGitProxy;
get UI(): IUIProxy;
get Assets(): IAssetsProxy;

@@ -100,0 +98,0 @@ get FileStorage(): LSPluginFileStorage;

@@ -18,2 +18,9 @@ import { LSPluginUser } from '../LSPlugin.user';

};
get Utils(): {
toClj: (input: any) => any;
jsxToClj: (input: any) => any;
toJs: (input: any) => any;
toKeyword: (input: any) => any;
toSymbol: (input: any) => any;
};
get pluginLocal(): PluginLocal;

@@ -20,0 +27,0 @@ invokeExperMethod(type: string, ...args: Array<any>): any;

@@ -24,2 +24,13 @@ import { safeSnakeCase } from '../helpers';

}
get Utils() {
const utils = this.ensureHostScope().logseq.sdk.utils;
const withCall = (name) => utils[safeSnakeCase(name)];
return {
toClj: withCall('toClj'),
jsxToClj: withCall('jsxToClj'),
toJs: withCall('toJs'),
toKeyword: withCall('toKeyword'),
toSymbol: withCall('toSymbol')
};
}
get pluginLocal() {

@@ -66,8 +77,10 @@ return this.ensureHostScope().LSPluginCore.ensurePlugin(this.ctx.baseInfo.id);

ensureHostScope() {
if (window === top) {
try {
const _ = window.top?.document;
}
catch (_e) {
console.error('Can not access host scope!');
return {};
}
return top;
return window.top;
}
}
{
"name": "@logseq/libs",
"version": "0.2.1",
"version": "0.2.2",
"description": "Logseq SDK libraries",

@@ -13,2 +13,3 @@ "main": "dist/lsplugin.user.js",

"dev:core": "npm run build:core -- --mode development --watch",
"generate:schema": "node scripts/extract-sdk-schema.js",
"build": "tsc && rm dist/*.js && npm run build:user",

@@ -31,2 +32,3 @@ "lint": "prettier --check \"src/**/*.{ts, js}\"",

"devDependencies": {
"ts-morph": "^22.0.0",
"@babel/core": "^7.20.2",

@@ -33,0 +35,0 @@ "@babel/preset-env": "^7.20.2",

@@ -33,1 +33,14 @@ ## @logseq/libs

https://discord.gg/KpN4eHY
#### Generate CLJS SDK wrappers
To regenerate the ClojureScript facade from the JS SDK declarations (keeping the same argument shapes as the JS APIs while auto-converting to/from CLJS data):
```bash
yarn run generate:schema # emits dist/logseq-sdk-schema.json
bb libs:generate-cljs-sdk # emits logseq/core.cljs and per-proxy files under target/generated-cljs
```
Non-proxy methods (those defined on `ILSPluginUser`, e.g. `ready`, `provide-ui`) land in `logseq.core`. Each proxy (`IAppProxy`, `IEditorProxy`, ...) is emitted to its own namespace such as `logseq.app` or `logseq.editor`, preserving the original JS argument ordering while automatically bean-converting CLJS data.
Pass `--out-dir` to change the output location or `--ns-prefix` to pick a different namespace root.

Sorry, the diff of this file is too big to display