@lobb-js/studio 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,47 +1 @@
1
- # Svelte + TS + Vite
2
-
3
- This template should help get you started developing with Svelte and TypeScript in Vite.
4
-
5
- ## Recommended IDE Setup
6
-
7
- [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8
-
9
- ## Need an official Svelte framework?
10
-
11
- Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12
-
13
- ## Technical considerations
14
-
15
- **Why use this over SvelteKit?**
16
-
17
- - It brings its own routing solution which might not be preferable for some users.
18
- - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19
-
20
- This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
21
-
22
- Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
23
-
24
- **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
25
-
26
- Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
27
-
28
- **Why include `.vscode/extensions.json`?**
29
-
30
- Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
31
-
32
- **Why enable `allowJs` in the TS template?**
33
-
34
- While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
35
-
36
- **Why is HMR not preserving my local component state?**
37
-
38
- HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
39
-
40
- If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
41
-
42
- ```ts
43
- // store.ts
44
- // An extremely simple external store
45
- import { writable } from 'svelte/store'
46
- export default writable(0)
47
- ```
1
+ # @lobb-js/studio
@@ -13,6 +13,7 @@
13
13
  executeExtensionsOnStartup,
14
14
  loadExtensions,
15
15
  } from "../extensions/extensionUtils";
16
+ import extensionMap from 'virtual:lobb-studio-extensions';
16
17
  import { mediaQueries } from "../utils";
17
18
  import Home from "./routes/home.svelte";
18
19
  import DataModel from "./routes/data_model/dataModel.svelte";
@@ -21,11 +22,10 @@
21
22
  import Extension from "./routes/extensions/extension.svelte";
22
23
 
23
24
  interface StudioProps {
24
- extensions?: any[];
25
25
  lobbUrl?: string;
26
26
  }
27
27
 
28
- const { lobbUrl, extensions }: StudioProps = $props();
28
+ const { lobbUrl }: StudioProps = $props();
29
29
 
30
30
  const ctx = $state({
31
31
  lobbUrl: lobbUrl ?? "",
@@ -46,7 +46,7 @@
46
46
  cleanupRouter = initRouter();
47
47
  try {
48
48
  ctx.meta = await lobb.getMeta();
49
- ctx.extensions = await loadExtensions(lobb, ctx, extensions);
49
+ ctx.extensions = await loadExtensions(lobb, ctx, extensionMap);
50
50
  await executeExtensionsOnStartup(lobb, ctx);
51
51
  status = "ready";
52
52
  } catch (err) {
@@ -1,5 +1,4 @@
1
1
  interface StudioProps {
2
- extensions?: any[];
3
2
  lobbUrl?: string;
4
3
  }
5
4
  declare const Studio: import("svelte").Component<StudioProps, {}, "">;
@@ -62,6 +62,8 @@ export function getField(ctx, fieldName, collectionName) {
62
62
  return getFields(ctx, collectionName)[fieldName];
63
63
  }
64
64
  export function getCollectionPrimaryField(ctx, collectionName) {
65
+ if (!ctx.meta.collections[collectionName])
66
+ return undefined;
65
67
  var collectionFields = ctx.meta.collections[collectionName].fields;
66
68
  var primaryFieldObject = Object.values(collectionFields).find(function (field) { return field.type === "string"; });
67
69
  if (primaryFieldObject &&
@@ -87,7 +89,7 @@ export function getCollectionParamsFields(ctx, collectionName, allFields) {
87
89
  var columns = [];
88
90
  for (var index = 0; index < foreignFields.length; index++) {
89
91
  var foreignField = foreignFields[index];
90
- if (!foreignField.collection) {
92
+ if (!foreignField.collection || !ctx.meta.collections[foreignField.collection]) {
91
93
  continue;
92
94
  }
93
95
  columns.push("".concat(foreignField.field, ".id"));
@@ -3,8 +3,7 @@ import type { LobbClient } from "@lobb-js/sdk";
3
3
  import type { CTX } from "../store.types";
4
4
  export declare function getComponents(): Components;
5
5
  export declare function getExtensionUtils(lobb: LobbClient, ctx: CTX): ExtensionUtils;
6
- export declare function getExtensionsThatHasDash(ctx: CTX): string[];
7
- export declare function loadExtensions(lobb: LobbClient, ctx: CTX, studioExtensions?: any[]): Promise<Record<string, Extension>>;
6
+ export declare function loadExtensions(lobb: LobbClient, ctx: CTX, extensionMap?: Record<string, any>): Promise<Record<string, Extension>>;
8
7
  export declare function loadExtensionComponents(ctx: CTX, name: string, filterByExtensions?: string[]): any[];
9
8
  export declare function executeExtensionsOnStartup(lobb: LobbClient, ctx: CTX): Promise<void>;
10
9
  export declare function getDashboardNavs(ctx: CTX): DashboardNavs;
@@ -104,61 +104,19 @@ export function getExtensionUtils(lobb, ctx) {
104
104
  intlDate: intlDate,
105
105
  };
106
106
  }
107
- export function getExtensionsThatHasDash(ctx) {
108
- var extensionNames = Object.keys(ctx.meta.extensions);
109
- var extensionsWithDashExt = [];
110
- for (var index = 0; index < extensionNames.length; index++) {
111
- var extensionName = extensionNames[index];
112
- var extensionMeta = ctx.meta.extensions[extensionName];
113
- if (extensionMeta.hasDashboardExtension) {
114
- extensionsWithDashExt.push(extensionName);
115
- }
116
- }
117
- return extensionsWithDashExt;
118
- }
119
- export function loadExtensions(lobb, ctx, studioExtensions) {
120
- return __awaiter(this, void 0, Promise, function () {
121
- var extensions, index, studioExtension, extensionsThatHasDash, index, extensionName, _a, _b, error_1;
122
- var _c;
123
- return __generator(this, function (_d) {
124
- switch (_d.label) {
125
- case 0:
126
- extensions = {};
127
- if (studioExtensions) {
128
- for (index = 0; index < studioExtensions.length; index++) {
129
- studioExtension = studioExtensions[index](getExtensionUtils(lobb, ctx));
130
- extensions[studioExtension.name] = studioExtension;
131
- }
132
- }
133
- extensionsThatHasDash = getExtensionsThatHasDash(ctx);
134
- index = 0;
135
- _d.label = 1;
136
- case 1:
137
- if (!(index < extensionsThatHasDash.length)) return [3 /*break*/, 6];
138
- extensionName = extensionsThatHasDash[index];
139
- // Try to import locally-installed extension by name first
140
- if (extensionName && extensions[extensionName]) {
141
- return [3 /*break*/, 5];
142
- }
143
- _d.label = 2;
144
- case 2:
145
- _d.trys.push([2, 4, , 5]);
146
- _a = extensions;
147
- _b = extensionName;
148
- return [4 /*yield*/, import(
149
- /* @vite-ignore */ "".concat(ctx.lobbUrl, "/api/extensions/").concat(extensionName, "/dashboard?v=").concat((_c = ctx.meta.extensions[extensionName]) === null || _c === void 0 ? void 0 : _c.version))];
150
- case 3:
151
- _a[_b] = (_d.sent()).extension(getExtensionUtils(lobb, ctx));
152
- return [3 /*break*/, 5];
153
- case 4:
154
- error_1 = _d.sent();
155
- console.warn("Failed to load remote extension ".concat(extensionName), error_1);
156
- return [3 /*break*/, 5];
157
- case 5:
158
- index++;
159
- return [3 /*break*/, 1];
160
- case 6: return [2 /*return*/, extensions];
107
+ export function loadExtensions(lobb_1, ctx_1) {
108
+ return __awaiter(this, arguments, Promise, function (lobb, ctx, extensionMap) {
109
+ var extensions, extensionNames, index, extensionName, studioExtension;
110
+ if (extensionMap === void 0) { extensionMap = {}; }
111
+ return __generator(this, function (_a) {
112
+ extensions = {};
113
+ extensionNames = Object.keys(extensionMap).filter(function (name) { return ctx.meta.extensions[name] != null; });
114
+ for (index = 0; index < extensionNames.length; index++) {
115
+ extensionName = extensionNames[index];
116
+ studioExtension = extensionMap[extensionName](getExtensionUtils(lobb, ctx));
117
+ extensions[studioExtension.name] = studioExtension;
161
118
  }
119
+ return [2 /*return*/, extensions];
162
120
  });
163
121
  });
164
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -14,7 +14,11 @@
14
14
  "svelte": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts"
16
16
  },
17
- "./vite-plugins": "./vite-plugins/index.js"
17
+ "./app.css": "./src/app.css",
18
+ "./vite-plugins": {
19
+ "types": "./vite-plugins/index.d.ts",
20
+ "default": "./vite-plugins/index.js"
21
+ }
18
22
  },
19
23
  "scripts": {
20
24
  "dev": "vite",
@@ -79,7 +83,7 @@
79
83
  "@codemirror/theme-one-dark": "^6.1.3",
80
84
  "@codemirror/view": "^6.39.12",
81
85
  "@dagrejs/dagre": "^1.1.5",
82
- "@lobb-js/sdk": "0.1.3",
86
+ "@lobb-js/sdk": "0.1.4",
83
87
  "@lucide/svelte": "^0.563.1",
84
88
  "@tailwindcss/vite": "^4.1.18",
85
89
  "@wjfe/n-savant": "^0.3.0",
@@ -0,0 +1,8 @@
1
+ declare module "virtual:lobb-studio-extensions" {
2
+ const extensions: Record<string, (utils: any) => any>;
3
+ export default extensions;
4
+ }
5
+
6
+ // Returns an array of Vite plugins. Typed as any[] to avoid version conflicts
7
+ // between the vite version used by packages/studio and the consumer's vite version.
8
+ export function lobbStudioPlugins(): any[];
@@ -1,16 +1,11 @@
1
1
  import { contextualLibAlias } from "./contextual-lib-alias.js";
2
+ import { lobbWorkspaceOptimize } from "./monorepo-workspace.js";
3
+ import { lobbExtensionsPlugin } from "./lobb-extensions.js";
2
4
 
3
- /**
4
- * Returns an array of Vite plugins needed for Lobb Studio integration.
5
- *
6
- * Includes:
7
- * - monorepoImportResolver: Resolves @lobb-js/studio to /internal (monorepo) or default (standalone)
8
- * - contextualLibAlias: Resolves $lib imports contextually
9
- *
10
- * @returns {import('vite').Plugin[]}
11
- */
12
5
  export function lobbStudioPlugins() {
13
6
  return [
14
7
  contextualLibAlias(),
8
+ lobbWorkspaceOptimize(),
9
+ lobbExtensionsPlugin(),
15
10
  ];
16
11
  }
@@ -0,0 +1,73 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ // Generates a virtual module `virtual:lobb-studio-extensions` that:
5
+ // 1. Reads the consumer project's package.json
6
+ // 2. Finds all @lobb-js/lobb-ext-* deps that have a ./studio subpath export
7
+ // 3. Statically imports them as a name->factory map
8
+ // 4. Also globs local extension studio entry points
9
+ // Result: { "auth": authFactory, "storage": storageFactory, "myext": localFactory }
10
+ export function lobbExtensionsPlugin() {
11
+ const virtualModuleId = "virtual:lobb-studio-extensions";
12
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
13
+
14
+ return {
15
+ name: "lobb-studio-extensions",
16
+ resolveId(id) {
17
+ if (id === virtualModuleId) return resolvedVirtualModuleId;
18
+ },
19
+ load(id) {
20
+ if (id !== resolvedVirtualModuleId) return;
21
+
22
+ let pkg;
23
+ try {
24
+ pkg = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
25
+ } catch {
26
+ return `export default {};`;
27
+ }
28
+
29
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
30
+
31
+ // Find @lobb-js/lobb-ext-* packages that have a ./studio export
32
+ const extPackages = [];
33
+ for (const pkgName of Object.keys(deps)) {
34
+ if (!pkgName.startsWith("@lobb-js/lobb-ext-")) continue;
35
+ try {
36
+ const pkgJsonPath = resolve(process.cwd(), "node_modules", pkgName, "package.json");
37
+ const extPkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
38
+ if (extPkg.exports?.["./studio"]) {
39
+ const name = pkgName.replace("@lobb-js/lobb-ext-", "");
40
+ extPackages.push({ name, pkgName });
41
+ }
42
+ } catch {}
43
+ }
44
+
45
+ const imports = extPackages
46
+ .map(({ name, pkgName }) => `import ext_${name} from "${pkgName}/studio";`)
47
+ .join("\n");
48
+
49
+ const mapEntries = extPackages
50
+ .map(({ name }) => ` "${name}": ext_${name}`)
51
+ .join(",\n");
52
+
53
+ return `${imports}
54
+
55
+ const localModules = import.meta.glob('/extensions/*/studio/index.ts', { eager: true });
56
+ const localExtensions = Object.fromEntries(
57
+ Object.entries(localModules)
58
+ .map(([path, mod]) => {
59
+ const name = path.match(/\\/extensions\\/([^\\/]+)\\/studio/)?.[1];
60
+ return [name, mod.default];
61
+ })
62
+ .filter(([name]) => name != null)
63
+ );
64
+
65
+ const npmExtensions = {
66
+ ${mapEntries}
67
+ };
68
+
69
+ export default { ...npmExtensions, ...localExtensions };
70
+ `;
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,138 @@
1
+ import { readdirSync, lstatSync, readFileSync, realpathSync } from "node:fs";
2
+ import { join, resolve, dirname } from "node:path";
3
+
4
+ /**
5
+ * Auto-detects workspace-linked @lobb-js/* packages and configures Vite so
6
+ * that monorepo dev and standalone npm-install users both work without manual
7
+ * configuration:
8
+ *
9
+ * - exclude: Prevents vite-plugin-svelte's optimize-module from
10
+ * running esbuild on raw .svelte source files.
11
+ * - entries: Tells Vite to crawl workspace packages at startup so
12
+ * all their deps are discovered and pre-bundled upfront
13
+ * (avoids 504 timeouts on first browser load).
14
+ * - include: Re-includes leaf CJS deps of excluded packages so
15
+ * Vite still pre-bundles them for the browser.
16
+ * - holdUntilCrawlEnd: Blocks the first response until optimization is done.
17
+ * - server.fs.allow: Allows Vite to serve files from the monorepo root
18
+ * (needed to follow symlinks into workspace packages).
19
+ *
20
+ * For standalone users (real npm install, no symlinks) all lists are empty and
21
+ * the config has no effect.
22
+ *
23
+ * @returns {import('vite').Plugin}
24
+ */
25
+ export function lobbWorkspaceOptimize() {
26
+ return {
27
+ name: "lobb-workspace-optimize",
28
+ config(_, { command }) {
29
+ if (command !== "serve") return;
30
+
31
+ const workspacePkgs = getLobbWorkspacePackages();
32
+ if (workspacePkgs.length === 0) return;
33
+
34
+ return {
35
+ optimizeDeps: {
36
+ holdUntilCrawlEnd: true,
37
+ entries: getWorkspaceEntries(workspacePkgs),
38
+ exclude: workspacePkgs,
39
+ include: getCjsDepsOf(workspacePkgs),
40
+ },
41
+ server: {
42
+ fs: {
43
+ allow: [getMonorepoRoot(workspacePkgs)].filter(Boolean),
44
+ },
45
+ },
46
+ };
47
+ },
48
+ };
49
+ }
50
+
51
+ function getLobbWorkspacePackages() {
52
+ const dir = resolve("node_modules/@lobb-js");
53
+ try {
54
+ return readdirSync(dir)
55
+ .filter((pkg) => lstatSync(join(dir, pkg)).isSymbolicLink())
56
+ .map((pkg) => `@lobb-js/${pkg}`);
57
+ } catch {
58
+ return [];
59
+ }
60
+ }
61
+
62
+ function getMonorepoRoot(pkgNames) {
63
+ for (const name of pkgNames) {
64
+ try {
65
+ const realPath = realpathSync(resolve(`node_modules/${name}`));
66
+ // realPath is e.g. /root/lobb/packages/studio — go up to find the monorepo root
67
+ let dir = realPath;
68
+ for (let i = 0; i < 5; i++) {
69
+ const parent = dirname(dir);
70
+ if (parent === dir) break;
71
+ const pkgJson = tryReadJson(join(parent, "package.json"));
72
+ if (pkgJson?.workspaces) return parent;
73
+ dir = parent;
74
+ }
75
+ } catch { /* skip */ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function getWorkspaceEntries(pkgNames) {
81
+ const entries = [];
82
+ for (const name of pkgNames) {
83
+ try {
84
+ const realPath = realpathSync(resolve(`node_modules/${name}`));
85
+ const pkg = tryReadJson(join(realPath, "package.json"));
86
+ if (!pkg) continue;
87
+ const exportsField = pkg.exports;
88
+ const entry =
89
+ pkg.svelte ??
90
+ pkg.module ??
91
+ pkg.main ??
92
+ (typeof exportsField?.["."] === "string"
93
+ ? exportsField["."]
94
+ : exportsField?.["."]?.default);
95
+ if (typeof entry === "string") entries.push(join(realPath, entry));
96
+ } catch { /* skip */ }
97
+ }
98
+ return entries;
99
+ }
100
+
101
+ function readPkgFrom(name, baseDir) {
102
+ let dir = baseDir;
103
+ for (let i = 0; i < 8; i++) {
104
+ const pkg = tryReadJson(join(dir, "node_modules", name, "package.json"));
105
+ if (pkg) return pkg;
106
+ const parent = dirname(dir);
107
+ if (parent === dir) break;
108
+ dir = parent;
109
+ }
110
+ return null;
111
+ }
112
+
113
+ function getCjsDepsOf(pkgNames) {
114
+ const deps = [];
115
+ for (const name of pkgNames) {
116
+ try {
117
+ const realPath = realpathSync(resolve(`node_modules/${name}`));
118
+ const pkg = tryReadJson(join(realPath, "package.json"));
119
+ if (!pkg) continue;
120
+ for (const dep of Object.keys(pkg.dependencies ?? {})) {
121
+ const depPkg = readPkgFrom(dep, realPath);
122
+ if (!depPkg) continue;
123
+ const isCjs = depPkg.type !== "module";
124
+ const isLeaf = Object.keys(depPkg.dependencies ?? {}).length === 0;
125
+ if (isCjs && isLeaf) deps.push(dep);
126
+ }
127
+ } catch { /* skip */ }
128
+ }
129
+ return [...new Set(deps)];
130
+ }
131
+
132
+ function tryReadJson(filePath) {
133
+ try {
134
+ return JSON.parse(readFileSync(filePath, "utf-8"));
135
+ } catch {
136
+ return null;
137
+ }
138
+ }