This commit is contained in:
Rizky 2023-10-18 16:12:54 +07:00
parent 7b5b9bb9a0
commit e0984794de
16 changed files with 255 additions and 413 deletions

9
app/srv/api/comp.ts Normal file
View File

@ -0,0 +1,9 @@
import { apiContext } from "service-srv";
export const _ = {
url: "/_web/comp/:id",
async api(id: string) {
const { req, res } = apiContext(this);
return await db.component.findFirst({ where: { id } });
},
};

9
app/srv/api/page.ts Normal file
View File

@ -0,0 +1,9 @@
import { apiContext } from "service-srv";
export const _ = {
url: "/_web/page/:id",
async api(id: string) {
const { req, res } = apiContext(this);
return await db.page.findFirst({ where: { id } });
},
};

View File

@ -1,15 +1,19 @@
import { validate } from "uuid";
import { page } from "web-utils";
import { devLoader } from "../../render/live/dev-loader";
import { Live } from "../../render/live/live";
import { defaultLoader } from "../../render/live/logic/default-loader";
export default page({
url: "/live/:domain/**",
component: ({}) => {
params.site_id = params.domain;
let pathname = `/${params._ === "_" ? "" : params._}`;
if (validate(params._)) {
params.page_id = params._;
const arr = params._.split("/");
params.page_id = arr.shift();
pathname = `/${arr.join("/")}`;
}
(window as any).pathname = pathname;
navigator.serviceWorker.controller?.postMessage({
type: "add-cache",
@ -18,10 +22,9 @@ export default page({
return (
<Live
mode={"dev"}
domain={params.domain}
pathname={`/${params._ === "_" ? "" : params._}`}
loader={defaultLoader}
domain_or_siteid={params.domain}
pathname={pathname}
loader={devLoader}
/>
);
},

View File

@ -5,12 +5,12 @@ import {
initApi,
reloadDBAPI,
} from "../../../utils/script/init-api";
import { defaultLoader } from "../../live/logic/default-loader";
import { LSite } from "../../live/logic/global";
import { validateLayout } from "../../live/logic/layout";
import { jscript } from "../panel/script/script-element";
import importModule from "../tools/dynamic-import";
import { PG } from "./global";
import { devLoader } from "../../live/dev-loader";
export const w = window as unknown as {
basepath: string;
@ -70,7 +70,10 @@ export const initEditor = async (p: PG, site_id: string) => {
} catch (e) {}
const querySite = async () => {
const site = await defaultLoader.site(p as any, { id: site_id });
const site = await devLoader.site(p as any, {
type: "siteid",
id: site_id,
});
localStorage.setItem(`prasi-site-${site_id}`, JSON.stringify(site));
return site;

View File

@ -36,7 +36,7 @@ const PassProp: FC<Record<string,any> & {children: React.ReactNode; }>;
const PassChild: FC<{name: string}>;
const Preload: FC<{url: string[]}>;
const apiurl: string;
const prasiPageID: string;
const pageid: string;
type ITEM = {
id: string
name: string;

View File

@ -0,0 +1,117 @@
import { LSite, Loader } from "./logic/global";
const cache = { site: null as any, pages: [] as any, api: null };
const config = { serverurl: "" };
export const devLoader: Loader = {
async site(p, where) {
config.serverurl = serverurl;
const site = (await db.site.findFirst({
where:
where.type === "siteid" ? { id: where.id } : { domain: where.domain },
select: {
id: true,
config: true,
domain: true,
name: true,
js: true,
responsive: true,
js_compiled: true,
},
})) as unknown as LSite;
if (!site) {
return null;
}
const cgroups = await db.site_use_comp.findMany({
where: { id_site: site.id },
select: { use_id_site: true },
});
if (cgroups) {
site.cgroup_ids = [];
for (const id of cgroups.map((c: any) => c.use_id_site)) {
site.cgroup_ids.push(id);
}
}
const layout = await db.page.findFirst({
where: {
id_site: site.id,
name: { startsWith: "layout:" },
is_default_layout: true,
is_deleted: false,
},
select: { content_tree: true, id: true },
});
if (layout) {
const childs = (layout.content_tree as any).childs;
if (childs && childs.length > 0) {
site.layout = childs[0];
site.layout_id = layout.id;
}
}
cache.site = site;
return site;
},
async comp(p, id) {
const comp = await load(`${config.serverurl}/_web/comp/${id}`);
p.comps.all[id] = comp;
return comp;
},
npm(p, type, id) {
if (type === "site") {
return `${config.serverurl}/npm/site/${id}/site.js`;
}
return `${config.serverurl}/npm/page/${id}/page.js`;
},
async page(p, id) {
return await load(`${config.serverurl}/_web/page/${id}`);
},
async pages(p, id) {
let pages = [] as any;
/** load pages */
const pagesLocal = localStorage.getItem(`prasi-pages-[${id}]`);
if (pagesLocal) {
try {
pages = JSON.parse(pagesLocal);
} catch (e) {}
}
const loadPages = async () => {
return await db.page.findMany({
where: {
id_site: cache.site.id,
is_deleted: false,
name: { not: { startsWith: "layout:" } },
},
select: {
id: true,
name: true,
url: true,
},
});
};
if (pages.length === 0) {
pages = await loadPages();
localStorage.setItem(`prasi-pages-[${id}]`, JSON.stringify(pages));
} else {
loadPages().then((pages) => {
localStorage.setItem(`prasi-pages-[${id}]`, JSON.stringify(pages));
});
}
return pages;
},
};
const load = async (url: string) => {
const res = await fetch(url);
const text = await res.text();
const json = JSON.parse(text);
return json;
};

View File

@ -7,7 +7,7 @@ export const LPage = () => {
const p = useGlobal(LiveGlobal, "LIVE");
const mode = p.mode;
let childs = Object.values(p.page?.content_tree.childs || []);
let childs = Object.values(p.page?.content_tree?.childs || []);
if (p.layout.section && p.layout.content) {
childs = [p.layout.section];
}

View File

@ -7,11 +7,10 @@ import { initLive, w } from "./logic/init";
import { preload, routeLive } from "./logic/route";
export const Live: FC<{
domain: string;
domain_or_siteid: string;
pathname: string;
loader: Loader;
mode: "dev" | "prod";
}> = ({ domain, pathname, loader, mode = "dev" }) => {
}> = ({ domain_or_siteid, pathname, loader }) => {
const p = useGlobal(LiveGlobal, "LIVE");
p.loader = loader;
@ -19,8 +18,6 @@ export const Live: FC<{
preload(p, url);
};
if (mode === "prod") p.prod = true;
if (p.site.id) {
if (!p.mode && !!p.site.responsive) {
if (p.site.responsive === "all") {
@ -58,12 +55,20 @@ export const Live: FC<{
}, [p.site.responsive]);
if (p.status === "init") {
initLive(p, domain);
initLive(p, domain_or_siteid);
}
if (p.site.id) {
routeLive(p, pathname);
}
if (p.status === "not-found")
return (
<div className="absolute inset-0 flex items-center justify-center flex-col">
<div className="text-[40px]">404</div>
<div>NOT FOUND</div>
</div>
);
return <LPage />;
};

View File

@ -44,29 +44,18 @@ export const loadComponent = async (
if (typeof itemOrID !== "string") {
tree = itemOrID;
} else {
if (!!p.comps.pending[itemOrID]) {
await p.comps.pending[itemOrID];
} else {
loadSingleComponent(p, itemOrID);
const res = await p.comps.pending[itemOrID];
tree = res.content_tree;
}
await loadComponentById(p, itemOrID);
}
scanComponent(tree, compIds);
const promises = [...compIds]
.filter((id) => {
if (p.prod) {
if (!p.comps.all[id] && !p.comps.pending[id]) return true;
} else {
if (!p.comps.doc[id] && !p.comps.pending[id]) return true;
}
if (!p.comps.all[id] && !p.comps.pending[id]) return true;
return false;
})
.map(async (id) => {
const res = await loadSingleComponent(p, id);
await loadComponent(p, res.content_tree);
return res;
const comp = await loadComponentById(p, id);
await loadComponent(p, comp.content_tree);
});
if (promises.length > 0) {
@ -74,8 +63,14 @@ export const loadComponent = async (
}
};
const loadSingleComponent = (p: PG, comp_id: string) => {
return p.loader.comp(p, comp_id);
const loadComponentById = async (p: PG, id: string) => {
if (!!p.comps.pending[id]) {
return await p.comps.pending[id];
}
const res = p.loader.comp(p, id);
p.comps.pending[id] = res;
p.comps.all[id] = await res;
return p.comps.all[id];
};
export const instantiateComp = (

View File

@ -1,119 +0,0 @@
import { PRASI_COMPONENT } from "../../../utils/types/render";
import { WS_MSG_GET_COMP } from "../../../utils/types/ws";
import { LPage, LSite, Loader } from "./global";
import { wsend } from "./ws";
export const defaultLoader: Loader = {
async site(_, where) {
const site = (await db.site.findFirst({
where,
select: {
id: true,
config: true,
domain: true,
name: true,
js: true,
responsive: true,
js_compiled: true,
},
})) as unknown as LSite;
if (!site) {
return null;
}
const cgroups = await db.site_use_comp.findMany({
where: { id_site: site.id },
select: { use_id_site: true },
});
if (cgroups) {
site.cgroup_ids = [];
for (const id of cgroups.map((c: any) => c.use_id_site)) {
site.cgroup_ids.push(id);
}
}
const layout = await db.page.findFirst({
where: {
id_site: site.id,
name: { startsWith: "layout:" },
is_default_layout: true,
is_deleted: false,
},
select: { content_tree: true, id: true },
});
if (layout) {
const childs = (layout.content_tree as any).childs;
if (childs && childs.length > 0) {
site.layout = childs[0];
site.layout_id = layout.id;
}
}
return site;
},
async comp(p, comp_id) {
p.comps.pending[comp_id] = new Promise<PRASI_COMPONENT>(async (resolve) => {
p.comps.resolve[comp_id] = async (comp: PRASI_COMPONENT) => {
resolve(comp);
};
await wsend(
p,
JSON.stringify({
type: "get_comp",
comp_id: comp_id,
} as WS_MSG_GET_COMP)
);
});
return p.comps.pending[comp_id];
},
npm(p, type, id) {
if (type === "site") {
return `${serverurl}/npm/site/${id}/site.js`;
} else if (type === "page") {
return `${serverurl}/npm/page/${id}/page.js`;
}
return null as any;
},
async page(p, id) {
const res = await db.page.findFirst({
where: { id: id, name: { not: { startsWith: "layout:" } } },
select: {
id: true,
url: true,
name: true,
content_tree: true,
js_compiled: true,
},
});
let page: LPage;
if (res) {
page = {
id: res.id,
content_tree: res.content_tree as any,
js: res.js_compiled as any,
name: res.name,
url: res.url,
};
return page;
}
return null as unknown as LPage;
},
async pages(p, site_id: string) {
return (await db.page.findMany({
where: {
id_site: site_id,
name: { not: { startsWith: "layout:" } },
is_deleted: false,
},
select: { id: true, url: true },
})) as unknown as LPage[];
},
};

View File

@ -16,7 +16,6 @@ export type ItemMeta = {
comp?: {
id: string;
propval?: any;
mcomp?: MItem;
child_ids: Record<string, string>;
};
className?: string;
@ -36,7 +35,7 @@ export type LPage = {
id: string;
name: string;
url: string;
content_tree: IRoot;
content_tree?: IRoot;
js: string;
};
@ -60,7 +59,7 @@ export type LSite = {
export type Loader = {
site: (
p: PG,
where: { domain: string } | { id: string }
where: { type: "domain"; domain: string } | { type: "siteid"; id: string }
) => Promise<LSite | null>;
page: (p: PG, id: string) => Promise<LPage | null>;
pages: (p: PG, site_id: string) => Promise<LPage[]>;
@ -112,7 +111,6 @@ export const LiveGlobal = {
comps: {
pending: {} as Record<string, Promise<PRASI_COMPONENT>>,
resolve: {} as Record<string, (comp: PRASI_COMPONENT) => void>,
doc: {} as Record<string, CompDoc>,
all: {} as Record<string, PRASI_COMPONENT>,
},
script: {

View File

@ -32,7 +32,7 @@ export const w = window as unknown as {
};
};
export const initLive = async (p: PG, domain: string) => {
export const initLive = async (p: PG, domain_or_siteid: string) => {
if (p.status === "init") {
p.status = "loading";
@ -68,30 +68,12 @@ export const initLive = async (p: PG, domain: string) => {
/** load site */
let site = null as null | LSite;
if (!p.prod) {
try {
site = JSON.parse(localStorage.getItem(`prasi-site-${domain}`) || "");
} catch (e) {}
if (!site) {
site = await p.loader.site(
p,
validate(domain) ? { id: domain } : { domain }
);
localStorage.setItem(`prasi-site-${domain}`, JSON.stringify(site));
} else {
p.loader
.site(p, validate(domain) ? { id: domain } : { domain })
.then((site) => {
localStorage.setItem(`prasi-site-${domain}`, JSON.stringify(site));
});
}
} else {
site = await p.loader.site(
p,
validate(domain) ? { id: domain } : { domain }
);
}
site = await p.loader.site(
p,
validate(domain_or_siteid)
? { type: "siteid", id: domain_or_siteid }
: { type: "domain", domain: domain_or_siteid }
);
if (site) {
/** import site module */
@ -112,59 +94,11 @@ export const initLive = async (p: PG, domain: string) => {
await validateLayout(p);
if (p.prod) {
p.site.api_url = await initApi(site.config, "prod");
} else {
w.externalAPI = {
mode: (localStorage.getItem(`prasi-ext-api-mode-${p.site.id}`) ||
"prod") as any,
devUrl: localStorage.getItem(`prasi-ext-dev-url-${p.site.id}`) || "",
prodUrl:
localStorage.getItem(`prasi-ext-prod-url-${p.site.id}`) || "",
};
p.site.api_url = await initApi(site.config);
p.site.api_url = await initApi(site.config, "prod");
if (w.externalAPI.prodUrl !== p.site.api_url) {
w.externalAPI.prodUrl = p.site.api_url;
localStorage.setItem(
`prasi-ext-prod-url-${p.site.id}`,
p.site.api_url
);
}
if (w.externalAPI.mode === "dev" && w.externalAPI.devUrl) {
p.site.api_url = w.externalAPI.devUrl;
await reloadDBAPI(w.externalAPI.devUrl, "dev");
}
}
w.apiurl = p.site.api_url;
let pages = [];
if (!p.prod) {
/** load pages */
const pagesLocal = localStorage.getItem(`prasi-pages-[${domain}]`);
if (pagesLocal) {
try {
pages = JSON.parse(pagesLocal);
} catch (e) {}
}
if (pages.length === 0) {
pages = await p.loader.pages(p, site.id);
localStorage.setItem(
`prasi-pages-[${domain}]`,
JSON.stringify(pages)
);
} else {
p.loader.pages(p, site.id).then((pages) => {
localStorage.setItem(
`prasi-pages-[${domain}]`,
JSON.stringify(pages)
);
});
}
} else {
pages = await p.loader.pages(p, site.id);
}
let pages = await p.loader.pages(p, site.id);
/** execute site module */
const exec = (fn: string, scopes: any) => {
@ -198,9 +132,11 @@ export const initLive = async (p: PG, domain: string) => {
}
/** create router */
p.pages = {};
p.route = createRouter({ strictTrailingSlash: false });
if (pages && pages.length > 0) {
for (const page of pages) {
p.pages[page.id] = page;
p.route.insert(page.url, page);
}
}

View File

@ -4,18 +4,16 @@ import { w } from "../../../utils/types/general";
import { WS_MSG_GET_PAGE } from "../../../utils/types/ws";
import importModule from "../../editor/tools/dynamic-import";
import { loadComponent } from "./comp";
import { LPage, LiveGlobal, PG } from "./global";
import { liveWS, wsend } from "./ws";
import { LPage, PG } from "./global";
import { rebuildTree } from "./tree-logic";
import { liveWS, wsend } from "./ws";
export const routeLive = (p: PG, pathname: string) => {
if (p.status !== "loading") {
let page_id = "";
if (validate(pathname.substring(1))) {
page_id = pathname.substring(1);
} else {
const found = p.route.lookup(pathname);
if (p.status !== "loading" && p.status !== "not-found") {
let page_id = params.page_id;
if (!page_id) {
const found = p.route.lookup(pathname);
if (!found) {
p.status = "not-found";
} else {
@ -30,48 +28,34 @@ export const routeLive = (p: PG, pathname: string) => {
}
if (page_id) {
(window as any).prasiPageID = page_id;
(window as any).pageid = page_id;
const promises: Promise<void>[] = [];
if (page_id !== p.page?.id) {
p.page = p.pages[page_id];
p.treeMeta = {};
p.portal = {};
}
if (!p.page || !p.page.content_tree) {
promises.push(loadNpmPage(p, page_id));
if (!p.prod) {
promises.push(streamPage(p, page_id));
} else {
promises.push(loadPage(p, page_id));
}
} else {
if (!p.prod) {
streamPage(p, page_id);
}
promises.push(loadPage(p, page_id));
}
if (promises.length > 0) {
p.status = "loading";
Promise.all(promises).then(async () => {
await pageLoaded(p);
p.status = "ready";
p.render();
p.page = p.pages[page_id];
if (p.page) {
await pageLoaded(p);
p.render();
}
});
} else {
if (p.prod && !firstRender[page_id]) {
if (!firstRender[page_id]) {
firstRender[page_id] = true;
pageLoaded(p).then(p.render);
} else {
if (!p.prod) {
pageLoaded(p).then(() => {
if (stream.page_id !== page_id) {
stream.page_id = page_id;
p.render();
}
});
} else {
pageLoaded(p);
}
pageLoaded(p);
}
}
}
@ -91,7 +75,11 @@ const pageLoaded = async (p: PG) => {
export const preload = async (p: PG, pathname: string) => {
const found = p.route.lookup(pathname);
if (found) {
if (!p.pages[found.id] && !p.pagePreload[found.id]) {
if (
(!p.pages[found.id] ||
(p.pages[found.id] && !p.pages[found.id].content_tree)) &&
!p.pagePreload[found.id]
) {
p.pagePreload[found.id] = true;
const dbpage = await p.loader.page(p, found.id);
if (dbpage) {
@ -139,6 +127,7 @@ const loadPage = async (p: PG, id: string) => {
content_tree: page.content_tree as any,
js: (page as any).js_compiled as any,
};
console.log(p.pages[page.id]);
const cur = p.pages[page.id];
if (cur && cur.content_tree) {
@ -147,36 +136,6 @@ const loadPage = async (p: PG, id: string) => {
}
};
const stream = { page_id: "" };
const streamPage = (p: PG, id: string) => {
return new Promise<void>(async (resolve) => {
await liveWS(p);
p.mpageLoaded = async (mpage) => {
const dbpage = mpage.getMap("map").toJSON() as page;
p.pages[dbpage.id] = {
id: dbpage.id,
url: dbpage.url,
name: dbpage.name,
content_tree: dbpage.content_tree as any,
js: dbpage.js_compiled as any,
};
const page = p.pages[dbpage.id];
if (page && page.content_tree) {
await loadComponent(p, page.content_tree);
}
resolve();
};
wsend(
p,
JSON.stringify({
type: "get_page",
page_id: id,
} as WS_MSG_GET_PAGE)
);
});
};
export const extractNavigate = (str: string) => {
return [
...findBetween(str, `navigate(`, `)`),

View File

@ -9,10 +9,12 @@ export const rebuildTree = async (
p: PG,
_: { note: string; render?: boolean; reset?: boolean }
) => {
if (!p.page?.content_tree) return;
p.treePending = new Promise<void>(async (resolve) => {
const treeMeta = p.treeMeta;
if (p.page) {
let childs = Object.values(p.page.content_tree.childs || []);
let childs = Object.values(p.page.content_tree?.childs || []);
if (
p.layout.section &&
p.layout.content &&
@ -22,7 +24,7 @@ export const rebuildTree = async (
p.layout.content.type = "item";
if (p.layout.content.type === "item") {
p.layout.content.childs = p.page.content_tree.childs.map((e) => ({
p.layout.content.childs = p.page.content_tree?.childs.map((e) => ({
...e,
type: "item",
})) as IItem[];
@ -66,7 +68,7 @@ const walk = async (
const treeMeta = val.treeMeta;
let item = val.item as IContent;
if (item.hidden === 'all') return;
if (item.hidden === "all") return;
if (val.parent_comp) {
const pchild_ids = val.parent_comp.comp?.child_ids;
@ -85,9 +87,6 @@ const walk = async (
comp = {
id: item.component.id,
child_ids: {},
mcomp: p.prod
? undefined
: p.comps.doc[item.component.id]?.getMap("map").get("content_tree"),
};
}
@ -107,119 +106,48 @@ const walk = async (
if (item.type === "item" && item.component?.id) {
const cid = item.component.id;
if (p.prod) {
let comp = p.comps.all[cid];
if (!comp) {
await loadComponent(p, cid);
comp = p.comps.all[cid];
let comp = p.comps.all[cid];
if (!comp) {
await loadComponent(p, cid);
comp = p.comps.all[cid];
}
if (comp) {
if (!p.compInstance[item.id]) {
p.compInstance[item.id] = {};
}
const child_ids = p.compInstance[item.id];
const itemnew = instantiateComp(
p,
item,
{ type: "i", item: comp },
child_ids
);
for (const [k, v] of Object.entries(itemnew)) {
if (k !== "id") (item as any)[k] = v;
}
if (comp) {
if (!p.compInstance[item.id]) {
p.compInstance[item.id] = {};
}
const child_ids = p.compInstance[item.id];
const itemnew = instantiateComp(
p,
item,
{ type: "i", item: comp },
child_ids
);
const cprops = comp.content_tree.component?.props;
const iprops = item.component.props;
for (const [k, v] of Object.entries(itemnew)) {
if (k !== "id") (item as any)[k] = v;
}
if (cprops && iprops) {
for (const [name, mprop] of Object.entries(cprops)) {
const jsx_prop = iprops[name];
const cprops = comp.content_tree.component?.props;
const iprops = item.component.props;
if (jsx_prop) {
if (mprop.meta?.type === "content-element") {
let icontent = jsx_prop.content;
if (cprops && iprops) {
for (const [name, mprop] of Object.entries(cprops)) {
const jsx_prop = iprops[name];
if (jsx_prop) {
if (mprop.meta?.type === "content-element") {
let icontent = jsx_prop.content;
if (icontent)
await walk(p, {
treeMeta,
item: icontent,
parent_id: item.id,
parent_comp: val.parent_comp,
idx: mprop.idx,
isLayout: meta.isLayout,
});
}
}
}
}
}
} else {
let doc = p.comps.doc[cid];
if (!doc) {
await loadComponent(p, cid);
doc = p.comps.doc[cid];
}
if (doc) {
const mcomp = doc.getMap("map").get("content_tree") as MItem;
if (mcomp) {
if (!p.compInstance[item.id]) {
p.compInstance[item.id] = {};
}
const child_ids = p.compInstance[item.id];
const itemnew = instantiateComp(
p,
item,
{ type: "m", item: mcomp },
child_ids
);
for (const [k, v] of Object.entries(itemnew)) {
if (k !== "id") (item as any)[k] = v;
}
meta.comp = {
id: cid,
mcomp,
child_ids,
};
}
let cprops: [string, FNCompDef][] = Object.entries(
item.component?.props || {}
).sort((a, b) => {
return a[1].idx - b[1].idx;
});
if (mcomp) {
const mprops = mcomp.get("component")?.get("props");
const iprops = item.component.props;
if (mprops && iprops) {
for (const [name, cprop] of cprops) {
let mp = mprops.get(name);
if (mp) {
const mprop = mp?.toJSON() as FNCompDef;
const jsx_prop = iprops[name];
if (jsx_prop) {
if (mprop.meta?.type === "content-element") {
let icontent = jsx_prop.content;
if (icontent)
await walk(p, {
treeMeta,
item: cprop.content,
parent_id: item.id,
parent_comp: meta as any,
idx: mprop.idx,
isLayout: meta.isLayout,
});
}
}
}
if (icontent)
await walk(p, {
treeMeta,
item: icontent,
parent_id: item.id,
parent_comp: val.parent_comp,
idx: mprop.idx,
isLayout: meta.isLayout,
});
}
}
}

View File

@ -1,7 +1,7 @@
import { FC, useState } from "react";
import { createRoot } from "react-dom/client";
import { GlobalContext, defineReact, defineWindow } from "web-utils";
import { SiteLoader } from "./site-loader";
import { siteLoader } from "./site-loader";
const w = window as unknown as {
prasiContext: any;
@ -25,8 +25,7 @@ const Root: FC<{ url: URL; Live: any }> = ({ url, Live }) => {
<Live
domain={url.host}
pathname={location.pathname}
loader={SiteLoader}
mode="prod"
loader={siteLoader}
/>
</Provider>
);

View File

@ -16,7 +16,7 @@ export const startDevWatcher = async () => {
import { apiContext } from "service-srv";
export const _ = {
url: "/${filename?.substring(0, filename.length - 3)})}",
url: "/${filename?.substring(0, filename.length - 3)}",
async api() {
const { req, res } = apiContext(this);
return "This is ${filename}";