From 5f5beca4f5a8993ce06e036a6deef8e874eb85cc Mon Sep 17 00:00:00 2001 From: Marcos Candeia <marrcooos@gmail.com> Date: Wed, 11 Sep 2024 14:55:25 -0300 Subject: [PATCH] Migrating to use @deco/deco from jsr (#845) * Migrating to use @deco/deco from jsr Signed-off-by: Marcos Candeia <marrcooos@gmail.com> * Add necessary imports Signed-off-by: Marcos Candeia <marrcooos@gmail.com> * Use setcookie from std Signed-off-by: Marcos Candeia <marrcooos@gmail.com> * Upgrade deco version Signed-off-by: Marcos Candeia <marrcooos@gmail.com> --------- Signed-off-by: Marcos Candeia <marrcooos@gmail.com> --- admin/types.ts | 15 +- ai-assistants/actions/awsUploadImage.ts | 12 +- ai-assistants/actions/chat.ts | 50 ++---- ai-assistants/actions/describeImage.ts | 9 +- ai-assistants/actions/transcribeAudio.ts | 9 +- ai-assistants/chat/messages.ts | 149 +++++++---------- ai-assistants/mod.ts | 49 ++---- ai-assistants/runtime.ts | 3 +- ai-assistants/schema.ts | 13 +- ai-assistants/utils/blobConversion.ts | 9 +- algolia/mod.ts | 19 +-- algolia/sections/Analytics/Algolia.tsx | 50 ++---- algolia/workflows/index/product.ts | 42 +++-- analytics/components/DecoAnalytics.tsx | 38 ++--- analytics/loaders/DecoAnalyticsScript.ts | 33 +--- analytics/mod.ts | 10 +- anthropic/actions/code.ts | 13 +- anthropic/actions/stream.ts | 38 ++--- anthropic/mod.ts | 5 +- blog/mod.ts | 10 +- blog/utils/records.ts | 5 +- brand-assistant/loaders/assistant.ts | 7 +- brand-assistant/mod.ts | 10 +- commerce/mod.ts | 32 ++-- commerce/types.ts | 132 ++------------- compat/$live/handlers/devPage.ts | 17 +- compat/$live/handlers/router.ts | 16 +- compat/$live/loaders/state.ts | 31 ++-- compat/$live/mod.ts | 20 +-- compat/$live/sections/PageInclude.tsx | 10 +- compat/$live/sections/Slot.tsx | 13 +- .../functions/vtexLegacyProductDetailsPage.ts | 25 +-- compat/std/functions/vtexLegacyProductList.ts | 13 +- .../functions/vtexLegacyProductListingPage.ts | 28 +--- .../vtexLegacyRelatedProductsLoader.ts | 24 +-- compat/std/functions/vtexNavbar.ts | 15 +- .../std/functions/vtexProductDetailsPage.ts | 25 +-- compat/std/functions/vtexProductList.ts | 14 +- .../std/functions/vtexProductListingPage.ts | 30 +--- compat/std/functions/vtexSuggestions.ts | 19 +-- compat/std/functions/vtexWishlist.ts | 36 ++-- compat/std/mod.ts | 79 +++------ compat/std/runtime.ts | 3 +- compat/std/sections/Analytics.tsx | 18 +- crux/mod.ts | 6 +- crux/preview/Preview.tsx | 6 +- decohub/apps/vtex.ts | 4 +- decohub/mod.ts | 67 ++++---- deno.json | 24 +-- emailjs/mod.ts | 17 +- files/loaders/app.ts | 70 ++------ files/mod.ts | 9 +- htmx/mod.ts | 10 +- htmx/sections/Deferred.tsx | 20 +-- htmx/sections/htmx.tsx | 9 +- implementation/mod.ts | 12 +- konfidency/loaders/productDetailsPage.ts | 6 +- konfidency/mod.ts | 12 +- linx-impulse/mod.ts | 26 +-- linx-impulse/runtime.ts | 3 +- .../Analytics/LinxImpulsePageView.tsx | 85 +++------- .../sections/Script/LinxImpulseScript.tsx | 4 +- linx/loaders/page.ts | 10 +- linx/loaders/pages.ts | 10 +- linx/loaders/path.ts | 13 +- linx/mod.ts | 20 +-- linx/runtime.ts | 3 +- mailchimp/actions/subscribe.ts | 2 +- mailchimp/loaders/options/lists.ts | 5 +- mailchimp/mod.ts | 15 +- nuvemshop/mod.ts | 39 ++--- nuvemshop/runtime.ts | 3 +- nuvemshop/utils/types.ts | 39 +---- openai/mod.ts | 10 +- power-reviews/mod.ts | 19 +-- ra-trustvox/mod.ts | 12 +- ra-trustvox/sections/TrustvoxCertificate.tsx | 13 +- .../sections/TrustvoxProductReviews.tsx | 22 +-- ra-trustvox/sections/TrustvoxRateConfig.tsx | 32 +--- .../sections/TrustvoxStoreReviewsCarousel.tsx | 20 +-- records/deps.ts | 3 +- records/mod.ts | 16 +- records/utils.ts | 9 +- resend/README.md | 2 +- resend/mod.ts | 32 ++-- scripts/new.ts | 2 +- shopify/mod.ts | 19 +-- shopify/runtime.ts | 3 +- smarthint/loaders/productListingPage.ts | 27 +-- smarthint/mod.ts | 16 +- smarthint/runtime.ts | 3 +- .../sections/Analytics/SmarthintTracking.tsx | 78 +++------ sourei/mod.ts | 5 +- sourei/sections/Analytics/Sourei.tsx | 31 +--- typesense/mod.ts | 21 +-- typesense/workflows/index/product.ts | 33 ++-- utils/cookie.ts | 12 +- utils/fetch.ts | 35 ++-- utils/framework.tsx | 3 +- utils/http.ts | 82 ++++----- utils/weakcache.ts | 1 + verified-reviews/mod.ts | 5 +- verified-reviews/utils/client.ts | 65 +++----- vnda/actions/cart/simulation.ts | 6 +- vnda/mod.ts | 20 +-- vnda/runtime.ts | 3 +- .../VTEXPortalDataLayerCompatibility.tsx | 65 +++----- vtex/loaders/collections/list.ts | 31 ++-- .../intelligentSearch/productListingPage.ts | 129 ++++---------- vtex/loaders/options/productIdByTerm.ts | 14 +- vtex/mod.ts | 46 ++--- vtex/preview/Preview.tsx | 21 +-- vtex/runtime.ts | 3 +- vtex/sections/Analytics/Vtex.tsx | 27 +-- vtex/workflows/events.ts | 14 +- vtex/workflows/product/index.ts | 18 +- wake/mod.ts | 21 +-- wake/runtime.ts | 3 +- wap/mod.ts | 15 +- wap/runtime.ts | 3 +- weather/mod.ts | 10 +- website/actions/secrets/encrypt.ts | 6 +- website/components/Analytics.tsx | 28 +--- website/components/Clickhouse.tsx | 157 +++++++++--------- website/components/Events.tsx | 46 ++--- website/components/_Controls.tsx | 33 +--- website/flags/audience.ts | 26 ++- website/flags/everyone.ts | 19 +-- website/flags/flag.ts | 13 +- website/flags/multivariate/image.ts | 3 +- website/flags/multivariate/message.ts | 4 +- website/flags/multivariate/page.ts | 4 +- website/flags/multivariate/section.ts | 4 +- website/functions/requestToParam.ts | 17 +- website/handlers/fresh.ts | 87 ++++------ website/handlers/proxy.ts | 55 +----- website/handlers/redirect.ts | 4 +- website/handlers/router.ts | 117 ++++--------- website/handlers/sitemap.ts | 11 +- website/loaders/asset.ts | 9 +- website/loaders/extension.ts | 6 +- website/loaders/options/routes.ts | 15 +- website/loaders/options/urlParams.ts | 8 +- website/loaders/pages.ts | 5 +- website/loaders/redirects.ts | 5 +- website/loaders/secret.ts | 9 +- website/matchers/cookie.ts | 10 +- website/matchers/device.ts | 8 +- website/matchers/host.ts | 11 +- website/matchers/location.ts | 28 +--- website/matchers/multi.ts | 5 +- website/matchers/negate.ts | 5 +- website/matchers/pathname.ts | 24 +-- website/matchers/queryString.ts | 30 +--- website/matchers/site.ts | 5 +- website/matchers/userAgent.ts | 6 +- website/mod.ts | 60 ++----- website/pages/Page.tsx | 88 ++++------ website/sections/Rendering/Deferred.tsx | 42 +---- website/sections/Rendering/Lazy.tsx | 41 +---- website/utils/multivariate.ts | 13 +- workflows/actions/start.ts | 43 ++--- workflows/handlers/workflowRunner.ts | 12 +- workflows/initializer.ts | 4 +- workflows/loaders/events.ts | 9 +- workflows/loaders/get.ts | 3 +- workflows/mod.ts | 10 +- workflows/utils/awaiters.ts | 17 +- 168 files changed, 1147 insertions(+), 2845 deletions(-) create mode 100644 utils/weakcache.ts diff --git a/admin/types.ts b/admin/types.ts index 75ad8e5df..ad617df3b 100644 --- a/admin/types.ts +++ b/admin/types.ts @@ -1,23 +1,19 @@ -import { type Resolvable } from "deco/engine/core/resolver.ts"; import { type fjp } from "./deps.ts"; - +import { type Resolvable } from "@deco/deco"; export interface Pagination<T> { data: T[]; page: number; pageSize: number; total: number; } - export interface PatchState { type: "patch-state"; payload: fjp.Operation[]; revision: string; } - export interface FetchState { type: "fetch-state"; } - export interface StatePatched { type: "state-patched"; payload: fjp.Operation[]; @@ -25,24 +21,21 @@ export interface StatePatched { // Maybe add data and user info in here metadata?: unknown; } - export interface StateFetched { type: "state-fetched"; payload: State; } - export interface OperationFailed { type: "operation-failed"; code: "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR"; reason: string; } - -export type Acked<T> = T & { ack: string }; - +export type Acked<T> = T & { + ack: string; +}; export interface State { decofile: Record<string, Resolvable>; revision: string; } - export type Commands = PatchState | FetchState; export type Events = StatePatched | StateFetched | OperationFailed; diff --git a/ai-assistants/actions/awsUploadImage.ts b/ai-assistants/actions/awsUploadImage.ts index a5904a065..34eb20426 100644 --- a/ai-assistants/actions/awsUploadImage.ts +++ b/ai-assistants/actions/awsUploadImage.ts @@ -1,22 +1,17 @@ -import { logger } from "deco/observability/otel/config.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import base64ToBlob from "../utils/blobConversion.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; - +import { logger, meter, ValueType } from "@deco/deco/o11y"; const stats = { awsUploadImageError: meter.createCounter("assistant_aws_upload_error", { unit: "1", valueType: ValueType.INT, }), }; - export interface AWSUploadImageProps { file: string | ArrayBuffer | null; assistantIds?: AssistantIds; } - // TODO(ItamarRocha): Check if possible to upload straight to bucket instead of using presigned url async function getSignedUrl( mimetype: string, @@ -24,7 +19,6 @@ async function getSignedUrl( ): Promise<string> { const randomID = crypto.randomUUID(); const name = `${randomID}.${mimetype.split("/")[1]}`; - // Get signed URL from S3 const s3Params = { Bucket: ctx.assistantAwsProps?.assistantBucketName.get?.() ?? "", @@ -32,16 +26,13 @@ async function getSignedUrl( ContentType: mimetype, ACL: "public-read", }; - const uploadURL = await ctx.s3?.getSignedUrlPromise("putObject", s3Params); return uploadURL as string; } - async function uploadFileToS3(presignedUrl: string, data: Blob) { const response = await fetch(presignedUrl, { method: "PUT", body: data }); return response; } - // TODO(ItamarRocha): Rate limit export default async function awsUploadImage( awsUploadImageProps: AWSUploadImageProps, @@ -57,7 +48,6 @@ export default async function awsUploadImage( ); const uploadURL = await getSignedUrl(blobData.type, ctx); const uploadResponse = await uploadFileToS3(uploadURL, blobData); - if (!uploadResponse.ok) { stats.awsUploadImageError.add(1, { assistantId, diff --git a/ai-assistants/actions/chat.ts b/ai-assistants/actions/chat.ts index 00aac7ec5..10f777d37 100644 --- a/ai-assistants/actions/chat.ts +++ b/ai-assistants/actions/chat.ts @@ -1,15 +1,12 @@ import { AppContext } from "../mod.ts"; - -import { badRequest, notFound } from "deco/mod.ts"; import { messageProcessorFor } from "../chat/messages.ts"; import { Notify, Queue } from "../deps.ts"; - +import { badRequest, notFound } from "@deco/deco"; export interface Props { thread?: string; assistant: string; message?: string; } - /** * Processes messages from the message queue. * @param {Queue<ChatMessage>} q - The message queue. @@ -35,18 +32,15 @@ const process = async ( ]); } }; - export interface MessageContentText { type: "text"; value: string; options?: string[]; } - export interface MessageContentFile { type: "file"; fileId: string; } - export interface ReplyMessage { threadId: string; messageId: string; @@ -54,16 +48,13 @@ export interface ReplyMessage { content: Array<MessageContentText | MessageContentFile>; role: "user" | "assistant"; } - export interface FunctionCall { name: string; props: unknown; } - export interface FunctionCallReply<T> extends FunctionCall { response: T; } - export interface ReplyStartFunctionCall { threadId: string; messageId: string; @@ -76,17 +67,14 @@ export interface ReplyFunctionCalls<T> { type: "function_calls"; content: FunctionCallReply<T>[]; } - export type Reply<T> = | ReplyMessage | ReplyFunctionCalls<T> | ReplyStartFunctionCall; - export interface ChatMessage { text: string; reply: <T = unknown>(reply: Reply<T>) => void; } - /** * Initializes a WebSocket chat connection and processes incoming messages. * @param {Props} props - The properties for the chat session. @@ -98,7 +86,12 @@ export default async function openChat( props: Props, req: Request, ctx: AppContext, -): Promise<Response | { replies: Reply<unknown>[]; thread: string }> { +): Promise< + Response | { + replies: Reply<unknown>[]; + thread: string; + } +> { if (!props.assistant) { notFound(); } @@ -106,13 +99,11 @@ export default async function openChat( if (!assistant) { notFound(); } - const threads = ctx.openAI.beta.threads; const threadId = props.thread; const threadPromise = threadId ? threads.retrieve(threadId) : threads.create(); - const processorPromise = assistant.then(async (aiAssistant) => messageProcessorFor(aiAssistant, ctx, await threadPromise) ); @@ -128,7 +119,6 @@ export default async function openChat( }); return { replies, thread: (await threadPromise).id }; } - const { socket, response } = Deno.upgradeWebSocket(req); const abort = new Notify(); const messagesQ = new Queue<ChatMessage>(); @@ -138,7 +128,6 @@ export default async function openChat( reply: (replyMsg) => socket.send(JSON.stringify(replyMsg)), }); } - /** * Handles the WebSocket connection on open event. */ @@ -156,19 +145,17 @@ export default async function openChat( }), ); assistant.then((aiAssistant) => { - socket.send( - JSON.stringify({ - isWelcomeMessage: true, - threadId: aiAssistant.threadId, - assistantId: aiAssistant.id, - type: "message", - content: [{ - type: "text", - value: aiAssistant.welcomeMessage ?? "Welcome to the chat!", - }], - role: "assistant", - }), - ); + socket.send(JSON.stringify({ + isWelcomeMessage: true, + threadId: aiAssistant.threadId, + assistantId: aiAssistant.id, + type: "message", + content: [{ + type: "text", + value: aiAssistant.welcomeMessage ?? "Welcome to the chat!", + }], + role: "assistant", + })); }); }; /** @@ -177,7 +164,6 @@ export default async function openChat( socket.onclose = () => { abort.notifyAll(); }; - /** * Handles the WebSocket connection on message event. * @param {MessageEvent} event - The WebSocket message event. diff --git a/ai-assistants/actions/describeImage.ts b/ai-assistants/actions/describeImage.ts index edaa5d76d..22296dc35 100644 --- a/ai-assistants/actions/describeImage.ts +++ b/ai-assistants/actions/describeImage.ts @@ -1,10 +1,7 @@ -import { logger } from "deco/observability/otel/config.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; -import { shortcircuit } from "deco/engine/errors.ts"; - +import { logger, meter, ValueType } from "@deco/deco/o11y"; +import { shortcircuit } from "@deco/deco"; const stats = { promptTokens: meter.createHistogram("assistant_image_prompt_tokens", { description: "Tokens used in Sales Assistant Describe Image Input - OpenAI", @@ -20,13 +17,11 @@ const stats = { valueType: ValueType.INT, }), }; - export interface DescribeImageProps { uploadURL: string; userPrompt: string; assistantIds?: AssistantIds; } - // TODO(ItamarRocha): Rate limit // TODO(@ItamarRocha): Refactor to use https://github.com/deco-cx/apps/blob/main/openai/loaders/vision.ts export default async function describeImage( diff --git a/ai-assistants/actions/transcribeAudio.ts b/ai-assistants/actions/transcribeAudio.ts index f2489a311..453417b22 100644 --- a/ai-assistants/actions/transcribeAudio.ts +++ b/ai-assistants/actions/transcribeAudio.ts @@ -1,10 +1,7 @@ -import { logger } from "deco/observability/otel/config.ts"; import base64ToBlob from "../utils/blobConversion.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; import { AssistantIds } from "../types.ts"; -import { ValueType } from "deco/deps.ts"; import { AppContext } from "../mod.ts"; - +import { logger, meter, ValueType } from "@deco/deco/o11y"; const stats = { audioSize: meter.createHistogram("assistant_transcribe_audio_size", { description: @@ -20,13 +17,11 @@ const stats = { }, ), }; - export interface TranscribeAudioProps { file: string | ArrayBuffer | null; assistantIds?: AssistantIds; audioDuration: number; } - // TODO(ItamarRocha): Rate limit export default async function transcribeAudio( transcribeAudioProps: TranscribeAudioProps, @@ -41,14 +36,12 @@ export default async function transcribeAudio( }); throw new Error("Audio file is empty"); } - const blobData = base64ToBlob( transcribeAudioProps.file, "audio", transcribeAudioProps.assistantIds, ); const file = new File([blobData], "input.wav", { type: "audio/wav" }); - stats.audioSize.record(transcribeAudioProps.audioDuration, { assistant_id: assistantId, }); diff --git a/ai-assistants/chat/messages.ts b/ai-assistants/chat/messages.ts index 0dc3a5327..169eab460 100644 --- a/ai-assistants/chat/messages.ts +++ b/ai-assistants/chat/messages.ts @@ -1,22 +1,20 @@ -import { - AssistantCreateParams, - RequiredActionFunctionToolCall, - Thread, -} from "../deps.ts"; -import { threadMessageToReply, Tokens } from "../loaders/messages.ts"; -import { JSONSchema7, ValueType, weakcache } from "deco/deps.ts"; -import { lazySchemaFor } from "deco/engine/schema/lazy.ts"; -import { Context } from "deco/live.ts"; -import { meter } from "deco/observability/otel/metrics.ts"; +import { Context, type JSONSchema7, lazySchemaFor } from "@deco/deco"; +import { meter, ValueType } from "@deco/deco/o11y"; +import { weakcache } from "../../utils/weakcache.ts"; import { ChatMessage, FunctionCallReply, Reply, ReplyMessage, } from "../actions/chat.ts"; +import { + AssistantCreateParams, + RequiredActionFunctionToolCall, + Thread, +} from "../deps.ts"; +import { threadMessageToReply, Tokens } from "../loaders/messages.ts"; import { AIAssistant, AppContext } from "../mod.ts"; import { dereferenceJsonSchema } from "../schema.ts"; - const stats = { latency: meter.createHistogram("assistant_latency", { description: @@ -25,24 +23,20 @@ const stats = { valueType: ValueType.DOUBLE, }), }; - // Max length of instructions. The maximum context of the assistant is 32K chars. We use 25K for instructions to be safe. const MAX_INSTRUCTIONS_LENGTH = 25000; - const notUndefined = <T>(v: T | undefined): v is T => v !== undefined; - const toolsCache = new weakcache.WeakLRUCache({ cacheSize: 16, // up to 16 different schemas stored here. }); - /** * Builds assistant tools that can be used by OpenAI assistant to execute actions based on users requests. * @param assistant the assistant that will handle the request * @returns an array of available functions that can be used. */ -const appTools = async (assistant: AIAssistant): Promise< - AssistantCreateParams.AssistantToolsFunction[] -> => { +const appTools = async ( + assistant: AIAssistant, +): Promise<AssistantCreateParams.AssistantToolsFunction[]> => { const ctx = Context.active(); const assistantsKey = assistant.availableFunctions?.join(",") ?? "all"; const revision = await ctx.release!.revision(); @@ -56,60 +50,57 @@ const appTools = async (assistant: AIAssistant): Promise< ...runtime.manifest.loaders, ...runtime.manifest.actions, }); - const tools = functionKeys.map( - (functionKey) => { - const functionDefinition = btoa(functionKey); - const schema = schemas.definitions[functionDefinition]; - if ((schema as { ignoreAI?: boolean })?.ignoreAI) { - return undefined; - } - const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; - if (!propsRef) { - return undefined; - } - const dereferenced = dereferenceJsonSchema({ - $ref: propsRef, - ...schemas, - }); - if ( - dereferenced.type !== "object" || - (dereferenced.oneOf || dereferenced.anyOf || - dereferenced?.allOf || dereferenced?.enum || dereferenced?.not) - ) { - return undefined; - } - return { - type: "function" as const, - function: { - name: functionKey, - description: - `Usage for: ${schema?.description}. Example: ${schema?.examples}`, - parameters: { - ...dereferenced, - definitions: undefined, - root: undefined, - }, + const tools = functionKeys.map((functionKey) => { + const functionDefinition = btoa(functionKey); + const schema = schemas.definitions[functionDefinition]; + if ( + (schema as { + ignoreAI?: boolean; + })?.ignoreAI + ) { + return undefined; + } + const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; + if (!propsRef) { + return undefined; + } + const dereferenced = dereferenceJsonSchema({ + $ref: propsRef, + ...schemas, + }); + if ( + dereferenced.type !== "object" || + (dereferenced.oneOf || dereferenced.anyOf || + dereferenced?.allOf || dereferenced?.enum || dereferenced?.not) + ) { + return undefined; + } + return { + type: "function" as const, + function: { + name: functionKey, + description: + `Usage for: ${schema?.description}. Example: ${schema?.examples}`, + parameters: { + ...dereferenced, + definitions: undefined, + root: undefined, }, - }; - }, - ).filter(notUndefined); - + }, + }; + }).filter(notUndefined); toolsCache.set(ctx, tools); return tools; }); toolsCache.set(cacheKey, toolsPromise); return toolsPromise; }; - export interface ProcessorOpts { assistantId: string; instructions: string; } - const sleep = (ns: number) => new Promise((resolve) => setTimeout(resolve, ns)); - const cache: Record<string, unknown> = {}; - const invokeFor = ( ctx: AppContext, assistant: AIAssistant, @@ -126,9 +117,7 @@ const invokeFor = ( return async (call: RequiredActionFunctionToolCall) => { try { const props = JSON.parse(call.function.arguments || "{}"); - const cacheKey = `${call.function.arguments}${call.function.name}`; - const assistantProps = assistant?.useProps?.(props) ?? props; cache[cacheKey] ??= ctx.invoke( // deno-lint-ignore no-explicit-any @@ -180,11 +169,9 @@ export const messageProcessorFor = async ( `; const assistantId = await ctx.assistant.then((assistant) => assistant.id); const tools = await appTools(assistant); - // Update the assistant object with the thread and assistant id assistant.threadId = thread.id; assistant.id = assistantId; - /** * Processes an incoming chat message. * @param {ChatMessage} message - The incoming chat message. @@ -206,11 +193,9 @@ export const messageProcessorFor = async ( instructions: instructions.slice(0, MAX_INSTRUCTIONS_LENGTH), tools, }); - const messageId = run.id; // Wait for the assistant answer const functionCallReplies: FunctionCallReply<unknown>[] = []; - // Reply to the user const reply = (message: Reply<unknown>) => { assistant.onMessageSent?.({ @@ -222,7 +207,6 @@ export const messageProcessorFor = async ( }); return _reply(message); }; - assistant.onMessageReceived?.({ assistantId: run.assistant_id, threadId: thread.id, @@ -230,7 +214,6 @@ export const messageProcessorFor = async ( model: run.model, message: { type: "text", value: content }, }); - const invoke = invokeFor(ctx, assistant, (call, props) => { reply({ threadId: thread.id, @@ -252,21 +235,13 @@ export const messageProcessorFor = async ( response, }); }); - let runStatus; do { - runStatus = await threads.runs.retrieve( - thread.id, - run.id, - ); - + runStatus = await threads.runs.retrieve(thread.id, run.id); if (runStatus.status === "requires_action") { const actions = runStatus.required_action!; const outputs = actions.submit_tool_outputs; - - const tool_outputs = await Promise.all( - outputs.tool_calls.map(invoke), - ); + const tool_outputs = await Promise.all(outputs.tool_calls.map(invoke)); if (tool_outputs.length === 0) { const message: ReplyMessage = { messageId: Date.now().toString(), @@ -278,28 +253,19 @@ export const messageProcessorFor = async ( reply(message); return; } - await threads.runs.submitToolOutputs( - thread.id, - run.id, - { - tool_outputs, - }, - ); - runStatus = await threads.runs.retrieve( - thread.id, - run.id, - ); + await threads.runs.submitToolOutputs(thread.id, run.id, { + tool_outputs, + }); + runStatus = await threads.runs.retrieve(thread.id, run.id); } await sleep(500); } while (["in_progress", "queued"].includes(runStatus.status)); - const messages = await threads.messages.list(thread.id); const threadMessages = messages.data; const lastMsg = threadMessages .findLast((message) => message.run_id == run.id && message.role === "assistant" ); - if (!lastMsg) { // TODO(@mcandeia) in some cases the bot didn't respond anything. const message: ReplyMessage = { @@ -312,9 +278,7 @@ export const messageProcessorFor = async ( reply(message); return; } - const replyMessage = threadMessageToReply(lastMsg); - // multi tool use parallel seems to be some sort of openai bug, and it seems to have no solution yet. // https://community.openai.com/t/model-tries-to-call-unknown-function-multi-tool-use-parallel/490653 // It's an error that only happens every now and then. Open ai tries to call "multi_tool_use.parallel" function that doesn't even exist and isn't even in the OpenAI documentation @@ -339,7 +303,6 @@ export const messageProcessorFor = async ( assistant_id: run.assistant_id, }); } - if (functionCallReplies.length > 0) { reply({ threadId: thread.id, diff --git a/ai-assistants/mod.ts b/ai-assistants/mod.ts index 8ff8e0fbe..ae3b64a21 100644 --- a/ai-assistants/mod.ts +++ b/ai-assistants/mod.ts @@ -1,9 +1,4 @@ import AWS from "https://esm.sh/aws-sdk@2.1585.0"; -import { asResolved, isDeferred } from "deco/engine/core/resolver.ts"; -import { isAwaitable } from "deco/engine/core/utils.ts"; -import type { App, AppContext as AC } from "deco/mod.ts"; -import { AvailableActions, AvailableLoaders } from "deco/utils/invoke.types.ts"; -import { AppManifest } from "deco/types.ts"; import { deferred } from "std/async/deferred.ts"; import openai, { Props as OpenAIProps, @@ -13,7 +8,16 @@ import { Assistant } from "./deps.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { Secret } from "../website/loaders/secret.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { + type App, + type AppContext as AC, + type AppManifest, + asResolved, + type AvailableActions, + type AvailableLoaders, + isDeferred, +} from "@deco/deco"; +import { isAwaitable } from "@deco/deco/utils"; export type GPTModel = | "gpt-4-0613" | "gpt-4-0314" @@ -33,29 +37,24 @@ export interface AIAssistant<TManifest extends AppManifest = AppManifest> { * The name of the AI Assistant. */ name: string; - /** * Optional instructions or guidelines for the AI Assistant. */ instructions?: string; - /** * Optional array of prompts to provide context for the AI Assistant. */ prompts?: Prompt[]; - /** * Optional welcome message to be displayed when the chat session starts. */ welcomeMessage?: string; - /** * Optional list of available functions (actions or loaders) that the AI Assistant can perform. */ availableFunctions?: Array< AvailableActions<TManifest> | AvailableLoaders<TManifest> >; - /** * Optional function to customize the handling of properties (props) passed to the AI Assistant. * It takes a set of properties and returns a modified set of properties. @@ -63,37 +62,33 @@ export interface AIAssistant<TManifest extends AppManifest = AppManifest> { * @returns {unknown} - The modified properties. */ useProps?: (props: unknown) => unknown; - /** * Optional function to log the received messages from the user. * @param {Log} logInfo - User message / information. * @returns {void} - The modified properties. */ onMessageReceived?: (logInfo: Log) => void; - /** * Optional function to log the received messages sent by the assistant. * @param {Log} logInfo - Assistant message / information. * @returns {void} - The modified properties. */ onMessageSent?: (logInfo: Log) => void; - /** * The GPT model that will be used, if not specified the assistant model will be used. */ - model?: GPTModel | { custom: string }; - + model?: GPTModel | { + custom: string; + }; /** * The Id of the assistant */ id?: string; - /** * The Id of the assistant thread */ threadId?: string; } - export interface Log { assistantId: string; threadId: string; @@ -101,19 +96,16 @@ export interface Log { model: string; message: object; } - export interface Prompt { content: string; context: string; } - export interface AssistantAwsProps { assistantBucketRegion: Secret; accessKeyId: Secret; secretAccessKey: Secret; assistantBucketName: Secret; } - export interface Props extends OpenAIProps { /** * @description the assistant Id @@ -127,7 +119,6 @@ export interface Props extends OpenAIProps { assistantAwsProps?: AssistantAwsProps; s3?: AWS.S3; } - export interface State extends OpenAIState { instructions?: string; assistant: Promise<Assistant>; @@ -135,16 +126,15 @@ export interface State extends OpenAIState { assistantAwsProps?: AssistantAwsProps; s3?: AWS.S3; } - /** * @title Deco AI Assistant * @description Create AI assistants on deco.cx. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png */ -export default function App( - state: Props, -): App<Manifest, State, [ReturnType<typeof openai>]> { +export default function App(state: Props): App<Manifest, State, [ + ReturnType<typeof openai>, +]> { const openAIApp = openai(state); const assistantsAPI = openAIApp.state.openAI.beta.assistants; // Sets assistantId only if state.assistants exists @@ -189,10 +179,7 @@ export default function App( dependencies: [openAIApp], }; } - -export const onBeforeResolveProps = ( - props: Props, -) => { +export const onBeforeResolveProps = (props: Props) => { if (Array.isArray(props?.assistants)) { return { ...props, @@ -203,9 +190,7 @@ export const onBeforeResolveProps = ( } return props; }; - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/ai-assistants/runtime.ts b/ai-assistants/runtime.ts index 41d65a98d..da42a2435 100644 --- a/ai-assistants/runtime.ts +++ b/ai-assistants/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/ai-assistants/schema.ts b/ai-assistants/schema.ts index 674d776db..493aff815 100644 --- a/ai-assistants/schema.ts +++ b/ai-assistants/schema.ts @@ -1,15 +1,13 @@ -import { JSONSchema7 } from "deco/deps.ts"; - -const isJSONSchema = ( - v: unknown | JSONSchema7, -): v is JSONSchema7 & { +import { type JSONSchema7 } from "@deco/deco"; +const isJSONSchema = (v: unknown | JSONSchema7): v is JSONSchema7 & { $ref: string; } => { return (typeof v === "object" && ((v as JSONSchema7)?.$ref !== undefined)); }; - export function dereferenceJsonSchema( - schema: JSONSchema7 & { definitions?: Record<string, JSONSchema7> }, + schema: JSONSchema7 & { + definitions?: Record<string, JSONSchema7>; + }, ) { const resolveReference = ( obj: unknown, @@ -30,6 +28,5 @@ export function dereferenceJsonSchema( } return obj as JSONSchema7; }; - return resolveReference(schema, {}); } diff --git a/ai-assistants/utils/blobConversion.ts b/ai-assistants/utils/blobConversion.ts index 2b08f6d4f..826cba88c 100644 --- a/ai-assistants/utils/blobConversion.ts +++ b/ai-assistants/utils/blobConversion.ts @@ -1,6 +1,5 @@ -import { logger } from "deco/observability/otel/config.ts"; import { AssistantIds } from "../types.ts"; - +import { logger } from "@deco/deco/o11y"; export default function base64ToBlob( base64: string | ArrayBuffer | null, context: string, @@ -23,7 +22,6 @@ export default function base64ToBlob( }`); throw new Error("Expected a base64 string"); } - const parts = base64.match(regex); if (!parts || parts.length !== 3) { logger.error(`${ @@ -38,21 +36,16 @@ export default function base64ToBlob( `${context} Base64 string is not properly formatted: ${parts}`, ); } - const mimeType = parts[1]; // e.g., 'audio/png' or 'video/mp4' or 'audio/mp3' or 'image/png' const mediaData = parts[2]; - // Convert the base64 encoded data to a binary string const binaryStr = atob(mediaData); - // Convert the binary string to an array of bytes (Uint8Array) const length = binaryStr.length; const arrayBuffer = new Uint8Array(new ArrayBuffer(length)); - for (let i = 0; i < length; i++) { arrayBuffer[i] = binaryStr.charCodeAt(i); } - // Create and return the Blob object return new Blob([arrayBuffer], { type: mimeType }); } diff --git a/algolia/mod.ts b/algolia/mod.ts index 7724656f2..e607bcef2 100644 --- a/algolia/mod.ts +++ b/algolia/mod.ts @@ -1,27 +1,23 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import algolia from "https://esm.sh/algoliasearch@4.20.0"; import { createFetchRequester } from "npm:@algolia/requester-fetch@4.20.0"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC<ReturnType<typeof App>>; - export interface State { /** * @title Your Algolia App ID * @description https://dashboard.algolia.com/account/api-keys/all */ applicationId: string; - /** * @title Search API Key * @description https://dashboard.algolia.com/account/api-keys/all * @format password */ searchApiKey: string; - /** * @title Admin API Key * @description https://dashboard.algolia.com/account/api-keys/all @@ -29,32 +25,24 @@ export interface State { */ adminApiKey: Secret; } - /** * @title Algolia * @description Product search & discovery that increases conversions at scale. * @category Search * @logo https://raw.githubusercontent.com/deco-cx/apps/main/algolia/logo.png */ -export default function App( - props: State, -) { +export default function App(props: State) { const { applicationId, adminApiKey, searchApiKey } = props; - if (!adminApiKey) { throw new Error("Missing admin API key"); } - const stringAdminApiKey = typeof adminApiKey === "string" ? adminApiKey : adminApiKey?.get?.() ?? ""; - const client = algolia(applicationId, stringAdminApiKey, { requester: createFetchRequester(), // Fetch makes it perform mutch better }); - const state = { client, applicationId, searchApiKey }; - const app: App<Manifest, typeof state> = { manifest: { ...manifest, @@ -73,15 +61,12 @@ export default function App( }, state, }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/algolia/sections/Analytics/Algolia.tsx b/algolia/sections/Analytics/Algolia.tsx index 26d5a218b..d752c3038 100644 --- a/algolia/sections/Analytics/Algolia.tsx +++ b/algolia/sections/Analytics/Algolia.tsx @@ -1,5 +1,3 @@ -import { SectionProps } from "deco/blocks/section.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import insights from "npm:search-insights@2.9.0"; import { AddToCartEvent, @@ -8,13 +6,13 @@ import { ViewItemListEvent, } from "../../../commerce/types.ts"; import { AppContext } from "../../mod.ts"; - +import { type SectionProps } from "@deco/deco"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; declare global { interface Window { aa: typeof insights.default; } } - const setupAndListen = (appId: string, apiKey: string, version: string) => { function setupScriptTag() { globalThis.window.AlgoliaAnalyticsObject = "aa"; @@ -26,7 +24,6 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ); }; globalThis.window.aa.version = version; - const script = document.createElement("script"); script.setAttribute("async", ""); script.setAttribute( @@ -35,7 +32,6 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ); document.head.appendChild(script); } - function createUserToken() { if ( typeof crypto !== "undefined" && @@ -43,67 +39,55 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { ) { return crypto.randomUUID(); } - return (Math.random() * 1e9).toFixed(); } - function setupSession() { globalThis.window.aa("init", { appId, apiKey }); - const userToken = localStorage.getItem("ALGOLIA_USER_TOKEN") || createUserToken(); localStorage.setItem("ALGOLIA_USER_TOKEN", userToken); globalThis.window.aa("setUserToken", userToken); } - function setupEventListeners() { function attributesFromURL(href: string) { const url = new URL(href); const queryID = url.searchParams.get("algoliaQueryID"); const indexName = url.searchParams.get("algoliaIndex"); - // Not comming from an algolia search page if (!queryID || !indexName) { return null; } - return { queryID, indexName }; } - // deno-lint-ignore no-explicit-any function isSelectItemEvent(event: any): event is SelectItemEvent { return event.name === "select_item"; } - // deno-lint-ignore no-explicit-any function isAddToCartEvent(event: any): event is AddToCartEvent { return event.name === "add_to_cart"; } - function isViewItem( // deno-lint-ignore no-explicit-any event: any, ): event is ViewItemEvent | ViewItemListEvent { return event.name === "view_item" || event.name === "view_item_list"; } - - type WithID<T> = T & { item_id: string }; - + type WithID<T> = T & { + item_id: string; + }; const hasItemId = <T,>(item: T): item is WithID<T> => // deno-lint-ignore no-explicit-any typeof (item as any).item_id === "string"; - const PRODUCTS = "products"; const MAX_BATCH_SIZE = 20; - globalThis.window.DECO.events.subscribe((event) => { - if (!event) return; - + if (!event) { + return; + } const eventName = event.name; - if (isSelectItemEvent(event)) { const [item] = event.params.items; - if ( !item || !hasItemId(item) || @@ -115,9 +99,7 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { JSON.stringify(event, null, 2), ); } - const attr = attributesFromURL(item.item_url); - if (attr) { globalThis.window.aa("clickedObjectIDsAfterSearch", { eventName, @@ -134,16 +116,13 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { }); } } - if (isAddToCartEvent(event)) { const [item] = event.params.items; - const attr = attributesFromURL(globalThis.window.location.href) || attributesFromURL(item.item_url || ""); const objectIDs = event.params.items .filter(hasItemId) .map((i) => i.item_id); - if (attr) { globalThis.window.aa("convertedObjectIDsAfterSearch", { eventName, @@ -159,12 +138,10 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { }); } } - if (isViewItem(event)) { const objectIDs = event.params.items .filter(hasItemId) .map((i) => i.item_id); - for (let it = 0; it < objectIDs.length; it += MAX_BATCH_SIZE) { globalThis.window.aa("viewedObjectIDs", { eventName, @@ -175,16 +152,13 @@ const setupAndListen = (appId: string, apiKey: string, version: string) => { } }); } - setupScriptTag(); setupSession(); setupEventListeners(); }; - -function Analytics({ - applicationId, - searchApiKey, -}: SectionProps<typeof loader>) { +function Analytics( + { applicationId, searchApiKey }: SectionProps<typeof loader>, +) { return ( <script defer @@ -197,11 +171,9 @@ function Analytics({ /> ); } - /** @title Algolia Integration - Events */ export const loader = (_props: unknown, _req: Request, ctx: AppContext) => ({ applicationId: ctx.applicationId, searchApiKey: ctx.searchApiKey, }); - export default Analytics; diff --git a/algolia/workflows/index/product.ts b/algolia/workflows/index/product.ts index 50bd8ca06..5563e53d8 100644 --- a/algolia/workflows/index/product.ts +++ b/algolia/workflows/index/product.ts @@ -1,7 +1,7 @@ -import { WorkflowContext, WorkflowGen } from "deco/mod.ts"; import { Product } from "../../../commerce/types.ts"; import type { Manifest } from "../../manifest.gen.ts"; - +import { WorkflowContext } from "@deco/deco/blocks"; +import { type WorkflowGen } from "@deco/deco"; /** * @title Algolia integration - Product Event */ @@ -15,31 +15,27 @@ export default function Index(_: unknown) { const productID = product?.productID; const name = product?.name; const groupName = product?.isVariantOf?.name; - if (type !== "Product" || !productID) { throw new Error(`Workflow input expected Product but received ${type}`); } - - yield ctx.log( - "[Algolia] Started indexing Product:", - { productID, name, groupName, action }, - ); - - const taskID = yield ctx.invoke( - "algolia/actions/index/product.ts", - { product, action }, - ); - + yield ctx.log("[Algolia] Started indexing Product:", { + productID, + name, + groupName, + action, + }); + const taskID = yield ctx.invoke("algolia/actions/index/product.ts", { + product, + action, + }); if (typeof taskID === "number") { - yield ctx.invoke( - "algolia/actions/index/wait.ts", - { taskID }, - ); + yield ctx.invoke("algolia/actions/index/wait.ts", { taskID }); } - - yield ctx.log( - "[Algolia] Finished indexing Product:", - { productID, name, groupName, action }, - ); + yield ctx.log("[Algolia] Finished indexing Product:", { + productID, + name, + groupName, + action, + }); }; } diff --git a/analytics/components/DecoAnalytics.tsx b/analytics/components/DecoAnalytics.tsx index 38a72e997..c9c2fde9b 100644 --- a/analytics/components/DecoAnalytics.tsx +++ b/analytics/components/DecoAnalytics.tsx @@ -1,6 +1,5 @@ import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; export interface Props { /** * @description paths to be excluded. @@ -8,24 +7,19 @@ export interface Props { exclude?: string; domain?: string; } - declare global { interface Window { - plausible: ( - name: string, - params: { props: Record<string, string | boolean> }, - ) => void; + plausible: (name: string, params: { + props: Record<string, string | boolean>; + }) => void; } } - // This function should be self contained, because it is stringified! const snippet = () => { // Flags and additional dimentions const props: Record<string, string> = {}; - const trackPageview = () => globalThis.window.plausible?.("pageview", { props }); - // Attach pushState and popState listeners const originalPushState = history.pushState; if (originalPushState) { @@ -36,14 +30,13 @@ const snippet = () => { }; addEventListener("popstate", trackPageview); } - // 2000 bytes limit const truncate = (str: string) => `${str}`.slice(0, 990); - // setup plausible script and unsubscribe globalThis.window.DECO.events.subscribe((event) => { - if (!event || event.name !== "deco") return; - + if (!event || event.name !== "deco") { + return; + } if (event.params) { const { flags, page } = event.params; if (Array.isArray(flags)) { @@ -53,33 +46,29 @@ const snippet = () => { } props["pageId"] = truncate(`${page.id}`); } - trackPageview(); })(); - globalThis.window.DECO.events.subscribe((event) => { - if (!event) return; - + if (!event) { + return; + } const { name, params } = event; - - if (!name || !params || name === "deco") return; - + if (!name || !params || name === "deco") { + return; + } const values = { ...props }; for (const key in params) { // @ts-expect-error somehow typescript bugs const value = params[key]; - if (value !== null && value !== undefined) { values[key] = truncate( typeof value !== "object" ? value : JSON.stringify(value), ); } } - globalThis.window.plausible?.(name, { props: values }); }); }; - function Component({ exclude, domain }: Props) { return ( <Head> @@ -100,5 +89,4 @@ function Component({ exclude, domain }: Props) { </Head> ); } - export default Component; diff --git a/analytics/loaders/DecoAnalyticsScript.ts b/analytics/loaders/DecoAnalyticsScript.ts index 2803ad847..2f9489d92 100644 --- a/analytics/loaders/DecoAnalyticsScript.ts +++ b/analytics/loaders/DecoAnalyticsScript.ts @@ -1,22 +1,18 @@ -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; -import { Flag } from "deco/types.ts"; import { Script } from "../../website/types.ts"; import { AppContext } from "../mod.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; +import { type Flag } from "@deco/deco"; export type Props = { defer?: boolean; domain?: string; }; - declare global { interface Window { - plausible: ( - name: string, - params: { props: Record<string, string | boolean> }, - ) => void; + plausible: (name: string, params: { + props: Record<string, string | boolean>; + }) => void; } } - const snippet = () => { const parseCookies = (cookieString: string) => { const cookies: Record<string, string> = {}; @@ -26,7 +22,6 @@ const snippet = () => { }); return cookies; }; - const tryOrDefault = <R>(fn: () => R, defaultValue: R) => { try { return fn(); @@ -34,7 +29,6 @@ const snippet = () => { return defaultValue; } }; - const getFlagsFromCookies = (cookies: Record<string, string>) => { const flags: Flag[] = []; const segment = cookies["deco_segment"] @@ -43,27 +37,21 @@ const snippet = () => { {}, ) : {}; - segment.active?.forEach((flag: string) => flags.push({ name: flag, value: true }) ); segment.inactiveDrawn?.forEach((flag: string) => flags.push({ name: flag, value: false }) ); - return flags; }; - const _flags = getFlagsFromCookies(parseCookies(document.cookie)); const flags: Record<string, string | boolean> = {}; _flags.forEach((flag) => flags[flag.name] = flag.value); - const trackPageview = () => globalThis.window.plausible?.("pageview", { props: flags }); - // First load trackPageview(); - // Attach pushState and popState listeners const originalPushState = history.pushState; if (originalPushState) { @@ -75,18 +63,12 @@ const snippet = () => { addEventListener("popstate", trackPageview); } }; - -const loader = ( - props: Props, - _req: Request, - _ctx: AppContext, -): Script => { +const loader = (props: Props, _req: Request, _ctx: AppContext): Script => { const transformReq = () => { const dnsPrefetchLink = '<link rel="dns-prefetch" href="https://plausible.io/api/event" />'; const preconnectLink = '<link rel="preconnect" href="https://plausible.io/api/event" crossorigin="anonymous" />'; - // if you want to test it local, add ".local" to src // example: /script.manual.local.js const plausibleScript = `<script ${ @@ -94,14 +76,11 @@ const loader = ( } data-exclude="/proxy" ${ props.domain ? "data-domain=" + props.domain : "" } data-api="https://plausible.io/api/event" src="https://plausible.io/js/script.manual.hash.js"></script>`; - const flagsScript = `<script defer src="${ useScriptAsDataURI(snippet) }"></script>`; - return dnsPrefetchLink + preconnectLink + plausibleScript + flagsScript; }; return ({ src: transformReq }); }; - export default loader; diff --git a/analytics/mod.ts b/analytics/mod.ts index c2c2de86d..24b6df52c 100644 --- a/analytics/mod.ts +++ b/analytics/mod.ts @@ -1,24 +1,18 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC<ReturnType<typeof App>>; - // deno-lint-ignore no-explicit-any export type State = any; - /** * @title Deco Analytics * @description Measure your site traffic at a glance in a simple and modern web analytics dashboard. * @category Analytics * @logo https://raw.githubusercontent.com/deco-cx/apps/main/analytics/logo.png */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export const preview = () => { return { Component: PreviewContainer, diff --git a/anthropic/actions/code.ts b/anthropic/actions/code.ts index 80e312c78..3be3b01cb 100644 --- a/anthropic/actions/code.ts +++ b/anthropic/actions/code.ts @@ -1,6 +1,6 @@ -import { shortcircuit } from "deco/engine/errors.ts"; import { AppContext } from "../mod.ts"; import { Anthropic } from "../deps.ts"; +import { shortcircuit } from "@deco/deco"; export interface Props { /** * @description The system prompt to be used for the AI Assistant. @@ -29,27 +29,20 @@ export interface Props { */ max_tokens?: number; } - export default async function chat( - { - system, - messages, - model = "claude-3-opus-20240229", - max_tokens = 4096, - }: Props, + { system, messages, model = "claude-3-opus-20240229", max_tokens = 4096 }: + Props, _req: Request, ctx: AppContext, ) { if (!messages) { return shortcircuit(new Response("No messages provided", { status: 400 })); } - const msg = await ctx.anthropic.messages.create({ system, model, max_tokens, messages, }); - return msg; } diff --git a/anthropic/actions/stream.ts b/anthropic/actions/stream.ts index da45087f6..67e99e47f 100644 --- a/anthropic/actions/stream.ts +++ b/anthropic/actions/stream.ts @@ -1,12 +1,13 @@ -import { JSONSchema7 } from "deco/deps.ts"; -import { shortcircuit } from "deco/engine/errors.ts"; -import { lazySchemaFor } from "deco/engine/schema/lazy.ts"; -import { Context } from "deco/live.ts"; -import { readFromStream } from "deco/utils/http.ts"; import { dereferenceJsonSchema } from "../../ai-assistants/schema.ts"; import { Anthropic } from "../deps.ts"; import { AppContext } from "../mod.ts"; - +import { + Context, + type JSONSchema7, + lazySchemaFor, + shortcircuit, +} from "@deco/deco"; +import { readFromStream } from "@deco/deco/utils"; export interface Props { /** * @description The system prompt to be used for the AI Assistant. @@ -45,16 +46,13 @@ export interface Props { enableTools?: boolean; temperature?: number; } - const notUndefined = <T>(v: T | undefined): v is T => v !== undefined; - const pathFormatter = { encode: (path: string): string => path.replace(/\.ts/g, "").replace(/\//g, "__"), decode: (encodedPath: string): string => encodedPath.replace(/__/g, "/") + ".ts", }; - /** * Retrieves the available tools for the AI Assistant. * @param availableFunctions List of functions available for the AI Assistant. @@ -66,32 +64,30 @@ const getAppTools = async ( const ctx = Context.active(); const runtime = await ctx.runtime!; const schemas = await lazySchemaFor(ctx).value; - const functionKeys = availableFunctions ?? Object.keys({ ...runtime.manifest.loaders, ...runtime.manifest.actions, }); - const tools = functionKeys .map((functionKey) => { const functionDefinition = btoa(functionKey); const schema = schemas.definitions[functionDefinition]; - - if ((schema as { ignoreAI?: boolean })?.ignoreAI) { + if ( + (schema as { + ignoreAI?: boolean; + })?.ignoreAI + ) { return undefined; } - const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; if (!propsRef) { return undefined; } - const dereferenced = dereferenceJsonSchema({ $ref: propsRef, ...schemas, }); - if ( dereferenced.type !== "object" || dereferenced.oneOf || @@ -102,7 +98,6 @@ const getAppTools = async ( ) { return undefined; } - return { name: pathFormatter.encode(functionKey), description: @@ -116,10 +111,8 @@ const getAppTools = async ( }; }) .filter(notUndefined); - return tools as Anthropic.Beta.Tools.Tool[] | undefined; }; - /** * @title Anthropic chat streaming * @description Sends messages to the Anthropic API for processing. @@ -140,16 +133,13 @@ export default async function chat( if (!messages) { return shortcircuit(new Response("No messages provided", { status: 400 })); } - const tools = await getAppTools(availableFunctions ?? []); - const headers = { "anthropic-version": "2023-06-01", "content-type": "application/json", "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15", "x-api-key": ctx.token ?? "", }; - let payload: Anthropic.Beta.Tools.MessageCreateParamsStreaming = { system, messages, @@ -158,7 +148,6 @@ export default async function chat( temperature, stream: true, }; - if (enableTools) { payload = { ...payload, @@ -166,19 +155,16 @@ export default async function chat( tool_choice: { type: "auto" }, }; } - const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers, body: JSON.stringify(payload), }); - if (!response.ok) { return shortcircuit( new Response(await response.text(), { status: response.status }), ); } - return readFromStream(response, { shouldDecodeChunk: false, }); diff --git a/anthropic/mod.ts b/anthropic/mod.ts index c338c4453..aba45f11a 100644 --- a/anthropic/mod.ts +++ b/anthropic/mod.ts @@ -1,20 +1,17 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Secret } from "../website/loaders/secret.ts"; import { Anthropic } from "./deps.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface Props { /** * @title Anthropic API Key */ apiKey?: Secret; } - export interface State { anthropic: Anthropic; token?: string; } - /** * @title Anthropic * @description Interact with the Anthropic API. diff --git a/blog/mod.ts b/blog/mod.ts index 14c082add..257937f83 100644 --- a/blog/mod.ts +++ b/blog/mod.ts @@ -1,24 +1,18 @@ -import type { App, FnContext } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type FnContext } from "@deco/deco"; // deno-lint-ignore no-explicit-any export type State = any; - export type AppContext = FnContext<State, Manifest>; - /** * @title Deco Blog * @description Manage your posts. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/weather/logo.png */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export const preview = () => { return { Component: PreviewContainer, diff --git a/blog/utils/records.ts b/blog/utils/records.ts index 02744803c..6e19e463b 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,6 +1,5 @@ import { AppContext } from "../mod.ts"; -import { Resolvable } from "deco/engine/core/resolver.ts"; - +import { type Resolvable } from "@deco/deco"; export async function getRecordsByPath<T>( ctx: AppContext, path: string, @@ -9,10 +8,8 @@ export async function getRecordsByPath<T>( const resolvables: Record<string, Resolvable<T>> = await ctx.get({ __resolveType: "resolvables", }); - const current = Object.entries(resolvables).flatMap(([key, value]) => { return key.startsWith(path) ? value : []; }); - return (current as Record<string, T>[]).map((item) => item[accessor]); } diff --git a/brand-assistant/loaders/assistant.ts b/brand-assistant/loaders/assistant.ts index ea0295bd9..5d914dc94 100644 --- a/brand-assistant/loaders/assistant.ts +++ b/brand-assistant/loaders/assistant.ts @@ -1,12 +1,12 @@ // deno-lint-ignore-file ban-unused-ignore no-explicit-any -import type { ManifestOf } from "deco/mod.ts"; -import { logger } from "deco/observability/otel/config.ts"; import type { AIAssistant, Log, Prompt } from "../../ai-assistants/mod.ts"; import type { Category, Product, Suggestion } from "../../commerce/types.ts"; import type { Manifest as OpenAIManifest } from "../../openai/manifest.gen.ts"; import type vtex from "../../vtex/mod.ts"; import { Tokens } from "../../ai-assistants/loaders/messages.ts"; import type { AssistantPersonalization } from "../../ai-assistants/types.ts"; +import { type ManifestOf } from "@deco/deco"; +import { logger } from "@deco/deco/o11y"; export interface Props { name: string; productsSample?: Product[] | null; @@ -29,14 +29,11 @@ const removePropertiesRecursively = <T>(category: T): T => { if (typeof category !== "object" || category === null) { return category; } - const { hasChildren: _ignoreHasChildren, url: _ignoreUrl, ...rest } = category as any; - rest.children = rest.children.map(removePropertiesRecursively); return rest; }; - type VTEXManifest = ManifestOf<ReturnType<typeof vtex>>; // TODO(ItamarRocha): Add store name in props or gather it from elsewhere. const BASE_INSTRUCTIONS = diff --git a/brand-assistant/mod.ts b/brand-assistant/mod.ts index f2c387253..eeb17be85 100644 --- a/brand-assistant/mod.ts +++ b/brand-assistant/mod.ts @@ -1,28 +1,22 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type AppContext as AC } from "@deco/deco"; // deno-lint-ignore no-empty-interface export interface Props { } - /** * @title Deco Brand Assistant * @description A concierge for your ecommerce. * @category Sales channel * @logo https://raw.githubusercontent.com/deco-cx/apps/main/brand-assistant/logo.png */ -export default function App( - state: Props, -): App<Manifest, Props> { +export default function App(state: Props): App<Manifest, Props> { return { manifest, state, }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/commerce/mod.ts b/commerce/mod.ts index d9ecc00b0..3155c2920 100644 --- a/commerce/mod.ts +++ b/commerce/mod.ts @@ -1,4 +1,3 @@ -import { App, FnContext } from "deco/mod.ts"; import shopify, { Props as ShopifyProps } from "../shopify/mod.ts"; import vnda, { Props as VNDAProps } from "../vnda/mod.ts"; import vtex, { Props as VTEXProps } from "../vtex/mod.ts"; @@ -6,44 +5,39 @@ import wake, { Props as WakeProps } from "../wake/mod.ts"; import website, { Props as WebsiteProps } from "../website/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { bgYellow } from "std/fmt/colors.ts"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext<Props, Manifest>; - type CustomPlatform = { platform: "other"; }; - export type Props = WebsiteProps & { /** @deprecated Use selected commerce instead */ - commerce?: - | VNDAProps - | VTEXProps - | ShopifyProps - | WakeProps - | CustomPlatform; + commerce?: VNDAProps | VTEXProps | ShopifyProps | WakeProps | CustomPlatform; }; - type WebsiteApp = ReturnType<typeof website>; type CommerceApp = | ReturnType<typeof vnda> | ReturnType<typeof vtex> | ReturnType<typeof wake> | ReturnType<typeof shopify>; - -export default function Site( - state: Props, -): App<Manifest, Props, [WebsiteApp] | [WebsiteApp, CommerceApp]> { +export default function Site(state: Props): App< + Manifest, + Props, + [ + WebsiteApp, + ] | [ + WebsiteApp, + CommerceApp, + ] +> { const { commerce } = state; - const site = website(state); - if (commerce && commerce.platform !== "other") { console.warn( bgYellow("Deprecated"), "Commerce prop is now deprecated. Delete this prop and install the commerce platform app instead. This will be removed in the future", ); } - const ecommerce = commerce?.platform === "vnda" ? vnda(commerce) : commerce?.platform === "vtex" @@ -53,7 +47,6 @@ export default function Site( : commerce?.platform === "shopify" ? shopify(commerce) : null; - return { state, manifest: { @@ -81,5 +74,4 @@ export default function Site( dependencies: ecommerce ? [site, ecommerce] : [site], }; } - export { onBeforeResolveProps } from "../website/mod.ts"; diff --git a/commerce/types.ts b/commerce/types.ts index c489adfed..81967c7be 100644 --- a/commerce/types.ts +++ b/commerce/types.ts @@ -1,10 +1,8 @@ -import { type Flag } from "deco/types.ts"; - +import { type Flag } from "@deco/deco"; /** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */ export declare type WithContext<T extends Things> = T & { "@context": "https://schema.org"; }; - /** * An store category */ @@ -23,9 +21,7 @@ export interface Category { */ children?: Category[]; } - export declare type Things = Thing | Product | BreadcrumbList; - export interface Thing { "@type": "Thing"; /** IRI identifying the canonical address of this object. */ @@ -52,7 +48,6 @@ export interface Thing { /** URL of the item. */ url?: string; } - export interface MediaObject { /** Media type typically expressed using a MIME format (see IANA site and MDN reference) */ encodingFormat?: string; @@ -61,12 +56,10 @@ export interface MediaObject { /** Actual bytes of the media object, for example the image file or video file. */ contentUrl?: string; } - export interface CreativeWork { /** A thumbnail image relevant to the Thing */ thumbnailUrl?: string; } - export interface VideoObject extends MediaObject, CreativeWork, Omit<Thing, "@type" | "url"> { /** @@ -83,7 +76,6 @@ export interface VideoObject */ duration?: string; } - export interface ImageObject extends MediaObject, CreativeWork, Omit<Thing, "@type" | "url"> { /** @@ -95,7 +87,6 @@ export interface ImageObject */ url?: string; } - export interface PropertyValue extends Omit<Thing, "@type"> { "@type": "PropertyValue"; /** The upper value of some characteristic or property. */ @@ -119,7 +110,6 @@ export interface PropertyValue extends Omit<Thing, "@type"> { /** A secondary value that provides additional information on the original value, e.g. a reference temperature or a type of measurement. */ valueReference?: string; } - export interface AggregateRating { "@type": "AggregateRating"; /** The count of total number of ratings. */ @@ -135,7 +125,6 @@ export interface AggregateRating { /** A short explanation (e.g. one to two sentences) providing background context and other information that led to the conclusion expressed in the rating. This is particularly applicable to ratings associated with "fact check" markup using ClaimReview. */ ratingExplanation?: string; } - export declare type ItemAvailability = | "https://schema.org/BackOrder" | "https://schema.org/Discontinued" @@ -147,17 +136,14 @@ export declare type ItemAvailability = | "https://schema.org/PreOrder" | "https://schema.org/PreSale" | "https://schema.org/SoldOut"; - export declare type OfferItemCondition = | "https://schema.org/DamagedCondition" | "https://schema.org/NewCondition" | "https://schema.org/RefurbishedCondition" | "https://schema.org/UsedCondition"; - export interface QuantitativeValue { value?: number; } - export declare type PriceTypeEnumeration = | "https://schema.org/InvoicePrice" | "https://schema.org/ListPrice" @@ -165,7 +151,6 @@ export declare type PriceTypeEnumeration = | "https://schema.org/MSRP" | "https://schema.org/SalePrice" | "https://schema.org/SRP"; - export declare type PriceComponentTypeEnumeration = | "https://schema.org/ActivationFee" | "https://schema.org/CleaningFee" @@ -173,26 +158,22 @@ export declare type PriceComponentTypeEnumeration = | "https://schema.org/Downpayment" | "https://schema.org/Installment" | "https://schema.org/Subscription"; - export declare type ReturnFeesEnumeration = | "https://schema.org/FreeReturn" | "https://schema.org/OriginalShippingFees" | "https://schema.org/RestockingFees" | "https://schema.org/ReturnFeesCustomerResponsibility" | "https://schema.org/ReturnShippingFees"; - export declare type ReturnMethodEnumeration = | "https://schema.org/KeepProduct" | "https://schema.org/ReturnAtKiosk" | "https://schema.org/ReturnByMail" | "https://schema.org/ReturnInStore"; - export declare type MerchantReturnEnumeration = | "https://schema.org/MerchantReturnFiniteReturnWindow" | "https://schema.org/MerchantReturnNotPermitted" | "https://schema.org/MerchantReturnUnlimitedWindow" | "https://schema.org/MerchantReturnUnspecified"; - export interface PriceSpecification extends Omit<Thing, "@type"> { "@type": "PriceSpecification"; /** The interval and unit of measurement of ordering quantities for which the offer or price specification is valid. This allows e.g. specifying that a certain freight charge is valid only for a certain quantity. */ @@ -214,7 +195,6 @@ export interface PriceSpecification extends Omit<Thing, "@type"> { */ priceCurrency?: string; } - export interface UnitPriceSpecification extends Omit<PriceSpecification, "@type"> { "@type": "UnitPriceSpecification"; @@ -227,28 +207,23 @@ export interface UnitPriceSpecification /** This property specifies the minimal quantity and rounding increment that will be the basis for the billing. The unit of measurement is specified by the unitCode property. */ billingIncrement?: number; } - export interface TeasersParameters { name: string; value: string; } - export interface TeasersConditions { minimumQuantity: number; parameters: TeasersParameters[]; } - export interface TeasersEffect { parameters: TeasersParameters[]; } - export interface Teasers { name: string; generalValues?: unknown; conditions: TeasersConditions; effects: TeasersEffect; } - export interface MonetaryAmount extends Omit<Thing, "@type"> { /** * The currency in which the monetary amount is expressed. @@ -269,7 +244,6 @@ export interface MonetaryAmount extends Omit<Thing, "@type"> { /** The value of a QuantitativeValue (including Observation) or property value node. */ value: boolean | number | string; } - export interface MerchantReturnPolicy extends Omit<Thing, "@type"> { "@type": "MerchantReturnPolicy"; /** Specifies either a fixed return date or the number of days (from the delivery date) that a product can be returned. Used when the returnPolicyCategory property is specified as MerchantReturnFiniteReturnWindow. Supersedes productReturnDays */ @@ -328,7 +302,6 @@ export interface Offer extends Omit<Thing, "@type"> { /** Specifies a MerchantReturnPolicy that may be applicable. */ hasMerchantReturnPolicy?: MerchantReturnPolicy; } - export interface AggregateOffer { "@type": "AggregateOffer"; /** @@ -360,7 +333,6 @@ export interface AggregateOffer { /** Specifies a MerchantReturnPolicy that may be applicable. */ hasMerchantReturnPolicy?: MerchantReturnPolicy; } - export interface ReviewPageResults { currentPageNumber?: number; nextPageUrl?: string; @@ -368,14 +340,12 @@ export interface ReviewPageResults { pagesTotal?: number; totalResults?: number; } - export interface ReviewPage { page: ReviewPageResults; id: string; review?: Review[]; aggregateRating?: AggregateRating; } - export interface Review extends Omit<Thing, "@type"> { "@type": "Review"; id?: string; @@ -404,7 +374,6 @@ export interface Review extends Omit<Thing, "@type"> { /** Medias */ media?: ReviewMedia[]; } - export interface ReviewMedia { type: "image" | "video"; url?: string; @@ -412,7 +381,6 @@ export interface ReviewMedia { likes?: number; unlikes?: number; } - export interface ReviewBrand { /** Brand Name */ name: string; @@ -421,14 +389,12 @@ export interface ReviewBrand { /** Brand website url */ url: string; } - export interface ReviewTag { /** Label of specific topic */ label?: string; /** Caracteristics about the topic */ value?: string[]; } - /** https://schema.org/Person */ export interface Person extends Omit<Thing, "@type"> { /** Email address. */ @@ -444,7 +410,6 @@ export interface Person extends Omit<Thing, "@type"> { /** The Tax / Fiscal ID of the organization or person, e.g. the TIN in the US or the CIF/NIF in Spain. */ taxID?: string; } - // NON SCHEMA.ORG Compliant. Should be removed ASAP export interface Author extends Omit<Thing, "@type"> { "@type": "Author"; @@ -457,11 +422,10 @@ export interface Author extends Omit<Thing, "@type"> { /** Author location */ location?: string; } - // TODO: fix this hack and use Product directly where it appears // Hack to prevent type self referencing and we end up with an infinite loop -export interface ProductLeaf extends Omit<Product, "isVariantOf"> {} - +export interface ProductLeaf extends Omit<Product, "isVariantOf"> { +} export interface ProductGroup extends Omit<Thing, "@type"> { "@type": "ProductGroup"; /** Indicates a {@link https://schema.org/Product Product} that is a member of this {@link https://schema.org/ProductGroup ProductGroup} (or {@link https://schema.org/ProductModel ProductModel}). */ @@ -477,13 +441,11 @@ export interface ProductGroup extends Omit<Thing, "@type"> { /** docs https://schema.org/gtin */ model?: string; } - export interface Brand extends Omit<Thing, "@type"> { "@type": "Brand"; /** Brand's image url */ logo?: string; } - export interface Answer extends Omit<Thing, "@type"> { text: string; /** The date that the anwser was published, in ISO 8601 date format.*/ @@ -493,7 +455,6 @@ export interface Answer extends Omit<Thing, "@type"> { /** Author of the */ author?: Author[]; } - export interface Question extends Omit<Thing, "@type" | "name"> { "@type": "Question"; answerCount: number; @@ -510,7 +471,6 @@ export interface Question extends Omit<Thing, "@type" | "name"> { /** Author of the */ author?: Author[]; } - export interface Product extends Omit<Thing, "@type"> { "@type": "Product"; /** @@ -552,10 +512,8 @@ export interface Product extends Omit<Thing, "@type"> { sku: string; /** A pointer to another product (or multiple products) for which this product is an accessory or spare part. */ isAccessoryOrSparePartFor?: Product[] | null; - questions?: Question[]; } - export interface ListItem<T = string> extends Omit<Thing, "@type"> { "@type": "ListItem"; /** An entity represented by an entry in a list or data feed (e.g. an 'artist' in a list of 'artists')’. */ @@ -563,7 +521,6 @@ export interface ListItem<T = string> extends Omit<Thing, "@type"> { /** The position of an item in a series or sequence of items. */ position: number; } - export interface ItemList<T = string> extends Omit<Thing, "@type"> { "@type": "ItemList"; /** @@ -577,11 +534,9 @@ export interface ItemList<T = string> extends Omit<Thing, "@type"> { /** The number of items in an ItemList. Note that some descriptions might not fully describe all items in a list (e.g., multi-page pagination); in such cases, the numberOfItems would be for the entire list. */ numberOfItems: number; } - export interface BreadcrumbList extends Omit<ItemList, "@type"> { "@type": "BreadcrumbList"; } - export type DayOfWeek = | "Monday" | "Tuesday" @@ -591,7 +546,6 @@ export type DayOfWeek = | "Saturday" | "Sunday" | "PublicHolidays"; - export interface OpeningHoursSpecification extends Omit<Thing, "@type"> { "@type": "OpeningHoursSpecification"; /** The closing hour of the place or service on the given day(s) of the week. */ @@ -605,7 +559,6 @@ export interface OpeningHoursSpecification extends Omit<Thing, "@type"> { /** The date after when the item is not valid. For example the end of an offer, salary period, or a period of opening hours. */ validThrough?: string; } - export interface ContactPoint extends Omit<Thing, "@type"> { "@type": "ContactPoint"; /** The geographic area where a service or offered item is provided. */ @@ -627,7 +580,6 @@ export interface ContactPoint extends Omit<Thing, "@type"> { /** The telephone number. */ telephone?: string; } - export interface PostalAddress extends Omit<ContactPoint, "@type"> { "@type": "PostalAddress"; /** The country. For example, USA. You can also provide the two-letter ISO 3166-1 alpha-2 country code. */ @@ -641,7 +593,6 @@ export interface PostalAddress extends Omit<ContactPoint, "@type"> { /** The street address. For example, 1600 Amphitheatre Pkwy. */ streetAddress?: string; } - export interface LocationFeatureSpecification extends Omit<PropertyValue, "@type"> { "@type": "LocationFeatureSpecification"; @@ -652,7 +603,6 @@ export interface LocationFeatureSpecification /** The date after when the item is not valid. For example the end of an offer, salary period, or a period of opening hours. */ validThrough?: string; } - export interface GeoCoordinates extends Omit<Thing, "@type"> { "@type": "GeoCoordinates"; /** The geographic area where a service or offered item is provided. */ @@ -668,7 +618,6 @@ export interface GeoCoordinates extends Omit<Thing, "@type"> { /** The postal code. For example, 94043. */ postalCode?: string; } - export interface GeoShape extends Omit<Thing, "@type"> { "@type": "GeoShape"; /** The GeoShape for the GeoCoordinates or GeoCircle. */ @@ -684,11 +633,9 @@ export interface GeoShape extends Omit<Thing, "@type"> { /** The postal code. For example, 94043. */ postalCode?: string; } - export interface About extends Omit<Thing, "@type"> { "@type": "About"; } - export interface Rating extends Omit<Thing, "@type"> { "@type": "Rating"; /** The author of this content or rating. Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably. */ @@ -711,19 +658,15 @@ export interface Rating extends Omit<Thing, "@type"> { /** The lowest value allowed in this rating system. */ worstRating?: number; } - export interface Organization extends Omit<Thing, "@type"> { "@type": "Organization"; } - export interface AdministrativeArea extends Omit<Thing, "@type"> { "@type": "AdministrativeArea"; } - export type CertificationStatus = | "CertificationActive" | "CertificationInactive"; - export interface Certification extends Omit<CreativeWork, "@type"> { "@type": "Certification"; /** The subject matter of the content. */ @@ -751,7 +694,6 @@ export interface Certification extends Omit<CreativeWork, "@type"> { /** The geographic area where the item is valid. Applies for example to a Permit, a Certification, or an EducationalOccupationalCredential. */ validIn?: AdministrativeArea; } - export interface PlaceLeaf extends Omit<Thing, "@type"> { "@type": "Place"; /** A property-value pair representing an additional characteristics of the entitity, e.g. a product feature or another characteristic for which there is no matching property in schema.org. */ @@ -811,7 +753,6 @@ export interface PlaceLeaf extends Omit<Thing, "@type"> { /** A page providing information on how to book a tour of some Place, such as an Accommodation or ApartmentComplex in a real estate setting, as well as other kinds of tours as appropriate. */ tourBookingPage?: string; } - /** Entities that have a somewhat fixed, physical extension. */ export interface Place extends PlaceLeaf { /** The basic containment relation between a place and one that contains it. Supersedes containedIn. Inverse property: containsPlace. */ @@ -837,7 +778,6 @@ export interface Place extends PlaceLeaf { /** Represents a relationship between two geometries (or the places they represent), relating a geometry to another that lies on it. As defined in DE-9IM. */ geoWithin?: PlaceLeaf; } - export interface FilterToggleValue { quantity: number; label: string; @@ -846,37 +786,34 @@ export interface FilterToggleValue { url: string; children?: Filter | null; } - export interface FilterRangeValue { min: number; max: number; } - export interface FilterBase { label: string; key: string; } - export interface FilterToggle extends FilterBase { "@type": "FilterToggle"; values: FilterToggleValue[]; quantity: number; } - export interface FilterRange extends FilterBase { "@type": "FilterRange"; values: FilterRangeValue; } - export type Filter = FilterToggle | FilterRange; -export type SortOption = { value: string; label: string }; +export type SortOption = { + value: string; + label: string; +}; export interface ProductDetailsPage { "@type": "ProductDetailsPage"; breadcrumbList: BreadcrumbList; product: Product; seo?: Seo | null; } - export type PageType = | "Brand" | "Category" @@ -887,7 +824,6 @@ export type PageType = | "Cluster" | "Search" | "Unknown"; - export interface PageInfo { currentPage: number; nextPage: string | undefined; @@ -896,7 +832,6 @@ export interface PageInfo { recordPerPage?: number | undefined; pageTypes?: PageType[]; } - export interface ProductListingPage { "@type": "ProductListingPage"; breadcrumb: BreadcrumbList; @@ -906,27 +841,26 @@ export interface ProductListingPage { sortOptions: SortOption[]; seo?: Seo | null; } - export interface Seo { title: string; description: string; canonical: string; noIndexing?: boolean; } - export interface Search { term: string; href?: string; hits?: number; - facets?: Array<{ key: string; values: string[] }>; + facets?: Array<{ + key: string; + values: string[]; + }>; } - export interface Suggestion { searches?: Search[]; products?: Product[] | null; hits?: number; } - /** @titleBy url */ export interface SiteNavigationElementLeaf { /** @@ -944,7 +878,6 @@ export interface SiteNavigationElementLeaf { /** URL of the item. */ url?: string; } - export interface SiteNavigationElement extends SiteNavigationElementLeaf { // TODO: The schema generator is not handling recursive types leading to an infinite loop // Lets circunvent this issue by enumerating the max allowed depth @@ -966,41 +899,36 @@ export interface SiteNavigationElement extends SiteNavigationElementLeaf { } >; } - /** @deprecated Use SiteNavigationElement instead */ export interface NavItem { label: string; href: string; - image?: { src?: string; alt?: string }; + image?: { + src?: string; + alt?: string; + }; } - /** @deprecated Use SiteNavigationElement instead */ export interface Navbar extends NavItem { // TODO: The schema generator is not handling recursive types leading in a infinite recursion loop // deno-lint-ignore no-explicit-any children?: any[]; } - // deno-lint-ignore no-explicit-any export interface IEvent<Params = any> { name: string; params: Params; } - // 3 letter ISO 4217 - Doc: https://en.wikipedia.org/wiki/ISO_4217#Active_codes type Currency = string; type Value = number; - interface WithItemId { item_id: string; } - interface WithItemName { item_name: string; } - type ItemIdentifier = WithItemId | WithItemName; - interface AnalyticsItemWithoutIdentifier { affiliation?: string; coupon?: string; @@ -1021,9 +949,7 @@ interface AnalyticsItemWithoutIdentifier { price?: Value; quantity: number; } - export type AnalyticsItem = AnalyticsItemWithoutIdentifier & ItemIdentifier; - export interface AddShippingInfoParams { currency?: Currency; value?: Value; @@ -1031,46 +957,38 @@ export interface AddShippingInfoParams { shipping_tier?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_shipping_info */ export interface AddShippingInfoEvent extends IEvent<AddShippingInfoParams> { name: "add_shipping_info"; } - export interface AddToCartParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_to_cart */ export interface AddToCartEvent extends IEvent<AddToCartParams> { name: "add_to_cart"; } - export interface AddToWishlistParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#add_to_wishlist */ export interface AddToWishlistEvent extends IEvent<AddToWishlistParams> { name: "add_to_wishlist"; } - export interface BeginCheckoutParams { currency: Currency; value: Value; items: AnalyticsItem[]; coupon?: string; } - /** docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#begin_checkout */ export interface BeginCheckoutEvent extends IEvent<BeginCheckoutParams> { name: "begin_checkout"; } - export interface LoginParams { method?: string; } @@ -1078,37 +996,30 @@ export interface LoginParams { export interface LoginEvent extends IEvent<LoginParams> { name: "login"; } - export interface RemoveFromCartParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#remove_from_cart */ export interface RemoveFromCartEvent extends IEvent<RemoveFromCartParams> { name: "remove_from_cart"; } - export interface SearchParams { search_term: string; } - export interface SearchEvent extends IEvent<SearchParams> { name: "search"; } - export interface SelectItemParams { item_list_id?: string; item_list_name?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#select_item */ export interface SelectItemEvent extends IEvent<SelectItemParams> { name: "select_item"; } - export interface SelectPromotionParams { creative_name?: string; creative_slot?: string; @@ -1116,45 +1027,37 @@ export interface SelectPromotionParams { promotion_name?: string; items?: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#select_promotion */ export interface SelectPromotionEvent extends IEvent<SelectPromotionParams> { name: "select_promotion"; } - export interface ViewCartParams { currency: Currency; value: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_cart */ export interface ViewCartEvent extends IEvent<ViewCartParams> { name: "view_cart"; } - export interface ViewItemParams { currency?: Currency; value?: Value; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#view_item */ export interface ViewItemEvent extends IEvent<ViewItemParams> { name: "view_item"; } - export interface ViewItemListParams { item_list_id?: string; item_list_name?: string; items: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_item_list */ export interface ViewItemListEvent extends IEvent<ViewItemListParams> { name: "view_item_list"; } - export interface ViewPromotionParams { creative_name?: string; creative_slot?: string; @@ -1162,26 +1065,21 @@ export interface ViewPromotionParams { promotion_name?: string; items?: AnalyticsItem[]; } - /** @docs https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_promotion */ export interface ViewPromotionEvent extends IEvent<ViewPromotionParams> { name: "view_promotion"; } - export interface Page { id: string | number; pathTemplate?: string; } - export interface Deco { flags: Flag[]; page: Page; } - export interface DecoEvent extends IEvent<Deco> { name: "deco"; } - export type AnalyticsEvent = | AddShippingInfoEvent | AddToCartEvent diff --git a/compat/$live/handlers/devPage.ts b/compat/$live/handlers/devPage.ts index 4ee13a6b8..1fd994e21 100644 --- a/compat/$live/handlers/devPage.ts +++ b/compat/$live/handlers/devPage.ts @@ -1,14 +1,12 @@ -import { Page } from "deco/blocks/page.tsx"; -import { context } from "deco/live.ts"; -import { adminUrlFor, isAdmin } from "deco/utils/admin.ts"; import Fresh from "../../../website/handlers/fresh.ts"; import { pageIdFromMetadata } from "../../../website/pages/Page.tsx"; import { AppContext } from "../mod.ts"; - +import { type Page } from "@deco/deco/blocks"; +import { context } from "@deco/deco"; +import { adminUrlFor, isAdmin } from "@deco/deco/utils"; export interface DevConfig { page: Page; } - /** * @title Private Fresh Page * @description Useful for pages under development. @@ -19,18 +17,13 @@ export default function DevPage(devConfig: DevConfig, ctx: AppContext) { const referer = req.headers.get("origin") ?? req.headers.get("referer"); const isOnAdmin = referer && isAdmin(referer); const pageId = pageIdFromMetadata(devConfig.page.metadata); - - if ( - context.isDeploy - ) { + if (context.isDeploy) { if (!referer || !isOnAdmin) { if (pageId === -1) { return Response.error(); } // redirect - return Response.redirect( - adminUrlFor(pageId, context.siteId), - ); + return Response.redirect(adminUrlFor(pageId, context.siteId)); } } return freshHandler(req, ctx); diff --git a/compat/$live/handlers/router.ts b/compat/$live/handlers/router.ts index 08a5b9671..37205272a 100644 --- a/compat/$live/handlers/router.ts +++ b/compat/$live/handlers/router.ts @@ -1,19 +1,16 @@ -import { Handler } from "deco/blocks/handler.ts"; -import { FnContext } from "deco/types.ts"; import { Routes } from "../../../website/flags/audience.ts"; import { router } from "../../../website/handlers/router.ts"; - +import { type Handler } from "@deco/deco/blocks"; +import { type FnContext } from "@deco/deco"; export interface RouterConfig { base?: string; routes: Routes; } - -export default function Router({ - routes: entrypoints, - base, -}: RouterConfig, ctx: FnContext): Handler { +export default function Router( + { routes: entrypoints, base }: RouterConfig, + ctx: FnContext, +): Handler { let routes = entrypoints; - if (base) { routes = []; for (const route of routes) { @@ -24,6 +21,5 @@ export default function Router({ ]; } } - return router(routes, {}, ctx.get.bind(ctx)); } diff --git a/compat/$live/loaders/state.ts b/compat/$live/loaders/state.ts index be6bce1a5..029c4d81c 100644 --- a/compat/$live/loaders/state.ts +++ b/compat/$live/loaders/state.ts @@ -1,12 +1,12 @@ -import { Accounts } from "deco/blocks/account.ts"; -import { Flag } from "deco/blocks/flag.ts"; -import { Loader } from "deco/blocks/loader.ts"; -import { Page } from "deco/blocks/page.tsx"; -import { Section } from "deco/blocks/section.ts"; -import { Resolvable } from "deco/engine/core/resolver.ts"; -import { LoaderContext } from "deco/mod.ts"; -import { Apps } from "deco/blocks/app.ts"; - +import { + type Accounts, + type Apps, + type Flag, + type Loader, + type Page, + type Section, +} from "@deco/deco/blocks"; +import { type LoaderContext, type Resolvable } from "@deco/deco"; /** * @titleBy key */ @@ -18,7 +18,6 @@ export interface Props { state: StateProp[]; apps?: Apps[]; } - /** * @title Shared application State Loader. * @description Set the application state using resolvables. @@ -28,15 +27,19 @@ export default async function StateLoader( _req: Request, { get }: LoaderContext, ): Promise<unknown> { - const mState: Promise<[string, Resolvable]>[] = []; - + const mState: Promise<[ + string, + Resolvable, + ]>[] = []; for (const { key, value } of state) { const resolved = get(value).then((resolved) => - [key, resolved] as [string, Resolvable] + [key, resolved] as [ + string, + Resolvable, + ] ); mState.push(resolved); } - return { state: Object.fromEntries(await Promise.all(mState)), apps, diff --git a/compat/$live/mod.ts b/compat/$live/mod.ts index 82ab8da5d..84e0687ac 100644 --- a/compat/$live/mod.ts +++ b/compat/$live/mod.ts @@ -1,29 +1,19 @@ -import type { App } from "deco/mod.ts"; - import webSite, { Props } from "../../website/mod.ts"; - -import { AppContext as AC } from "deco/blocks/app.ts"; import workflows from "../../workflows/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export { onBeforeResolveProps } from "../../website/mod.ts"; - export type AppContext = AC<ReturnType<typeof App>>; - export type { Props }; /** * @title $live */ -export default function App( - state: Props, -): App< - Manifest, - Props, - [ReturnType<typeof webSite>, ReturnType<typeof workflows>] -> { +export default function App(state: Props): App<Manifest, Props, [ + ReturnType<typeof webSite>, + ReturnType<typeof workflows>, +]> { const { resolvables: _ignoreResolvables, ...webSiteApp } = webSite(state); const workflowsApp = workflows({}); - return { state, manifest, diff --git a/compat/$live/sections/PageInclude.tsx b/compat/$live/sections/PageInclude.tsx index b1410bea1..52ab2a6a0 100644 --- a/compat/$live/sections/PageInclude.tsx +++ b/compat/$live/sections/PageInclude.tsx @@ -1,27 +1,21 @@ -/** TODO: Deprecate this file */ -import { Page } from "deco/blocks/page.tsx"; -import { notUndefined } from "deco/engine/core/utils.ts"; - import { Props as LivePageProps, renderSection, } from "../../../website/pages/Page.tsx"; - +import { type Page } from "@deco/deco/blocks"; +import { notUndefined } from "@deco/deco/utils"; export interface Props { page: Page; } - export const isLivePageProps = ( p: Page["props"] | LivePageProps, ): p is LivePageProps => { return (p as LivePageProps)?.sections !== undefined; }; - export default function PageInclude({ page }: Props) { if (!isLivePageProps(page?.props)) { return null; } - return ( <>{(page?.props?.sections ?? []).filter(notUndefined).map(renderSection)}</> ); diff --git a/compat/$live/sections/Slot.tsx b/compat/$live/sections/Slot.tsx index 227ecbacf..2f04cc1e6 100644 --- a/compat/$live/sections/Slot.tsx +++ b/compat/$live/sections/Slot.tsx @@ -1,5 +1,4 @@ -import { isSection, Section } from "deco/blocks/section.ts"; - +import { isSection, type Section } from "@deco/deco/blocks"; export type WellKnownSlots = | "content" | "footer" @@ -7,7 +6,6 @@ export type WellKnownSlots = | "analytics" | "design-system" | "SEO"; - export interface Props { /** * @description Enforces the slot to be fulfilled. @@ -19,29 +17,22 @@ export interface Props { */ name?: string | WellKnownSlots; } - export const CONTENT_SLOT_NAME = "content"; export const isContentSlot = (s: Section): boolean => { return isSection(s, "$live/sections/Slot.tsx") && s?.props?.name === CONTENT_SLOT_NAME; }; - export default function Slot(p: Props) { if (p?.required) { return ShowSlot(p); } return null; } - function ShowSlot(p: Props) { return ( - <div - class="border-dashed border-4 border-light-blue-500" - role="alert" - > + <div class="border-dashed border-4 border-light-blue-500" role="alert"> <p class="text-center text-2xl">{p.name}</p> </div> ); } - export const Preview = ShowSlot; diff --git a/compat/std/functions/vtexLegacyProductDetailsPage.ts b/compat/std/functions/vtexLegacyProductDetailsPage.ts index 7239fe5a5..1ec39e805 100644 --- a/compat/std/functions/vtexLegacyProductDetailsPage.ts +++ b/compat/std/functions/vtexLegacyProductDetailsPage.ts @@ -1,26 +1,15 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { ProductDetailsPage } from "../../../commerce/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX Catalog - Product Details Page (deprecated) * @description Works on routes of type /:slug/p * @deprecated true */ -const loaderV0: LoaderFunction< - null, - ProductDetailsPage | null, - AppContext -> = async ( - _req, - ctx, -) => { - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy - .productDetailsPage( - { slug: ctx.params.slug }, - ); - - return { data, status: data ? 200 : 404 }; -}; - +const loaderV0: LoaderFunction<null, ProductDetailsPage | null, AppContext> = + async (_req, ctx) => { + const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy + .productDetailsPage({ slug: ctx.params.slug }); + return { data, status: data ? 200 : 404 }; + }; export default loaderV0; diff --git a/compat/std/functions/vtexLegacyProductList.ts b/compat/std/functions/vtexLegacyProductList.ts index d3b445240..0e8a46a0f 100644 --- a/compat/std/functions/vtexLegacyProductList.ts +++ b/compat/std/functions/vtexLegacyProductList.ts @@ -1,7 +1,6 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { Product } from "../../../commerce/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; export interface Props { /** @description total number of items to display */ count: number; @@ -13,17 +12,12 @@ export interface Props { */ collection?: string[]; } - /** * @title VTEX Legacy - Search Products (deprecated) * @description Useful for shelves and static galleries. * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - Product[] | null, - AppContext -> = async ( +const loaderV0: LoaderFunction<Props, Product[] | null, AppContext> = async ( _req, ctx, props, @@ -31,11 +25,8 @@ const loaderV0: LoaderFunction< const p = props.query ? { term: props.query, count: props.count } : { collection: props.collection?.[0], count: props.count }; - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy .productList({ props: p }); - return { data, status: data ? 200 : 404 }; }; - export default loaderV0; diff --git a/compat/std/functions/vtexLegacyProductListingPage.ts b/compat/std/functions/vtexLegacyProductListingPage.ts index 89bfbb10e..6b1b84e02 100644 --- a/compat/std/functions/vtexLegacyProductListingPage.ts +++ b/compat/std/functions/vtexLegacyProductListingPage.ts @@ -1,31 +1,19 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { ProductListingPage } from "../../../commerce/types.ts"; import type { Props } from "../../../vtex/loaders/legacy/productListingPage.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX Catalog - Product Listing Page (deprecated) * @description Useful for category, search, brand and collection pages. * @deprecated */ -const loaderV0: LoaderFunction< - Props, - ProductListingPage | null, - AppContext -> = async ( - _req, - ctx, - props, -) => { - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy - .productListingPage( - { +const loaderV0: LoaderFunction<Props, ProductListingPage | null, AppContext> = + async (_req, ctx, props) => { + const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy + .productListingPage({ ...props, term: props.term || ctx.params["0"], - }, - ); - - return { data, status: data ? 200 : 404 }; -}; - + }); + return { data, status: data ? 200 : 404 }; + }; export default loaderV0; diff --git a/compat/std/functions/vtexLegacyRelatedProductsLoader.ts b/compat/std/functions/vtexLegacyRelatedProductsLoader.ts index 0d9d25a8e..bdcb7b4d0 100644 --- a/compat/std/functions/vtexLegacyRelatedProductsLoader.ts +++ b/compat/std/functions/vtexLegacyRelatedProductsLoader.ts @@ -1,8 +1,7 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { Product } from "../../../commerce/types.ts"; import type { CrossSellingType } from "../../../vtex/utils/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; export interface Props { /** * @title Related Products @@ -14,31 +13,22 @@ export interface Props { */ count?: number; } - /** * @title VTEX Catalog - Related Products (deprecated) * @description Works on routes of type /:slug/p * @deprecated */ -const loaderV0: LoaderFunction< - Props, - Product[] | null, - AppContext -> = async ( +const loaderV0: LoaderFunction<Props, Product[] | null, AppContext> = async ( _req, ctx, { crossSelling, count }, ) => { const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.legacy - .relatedProductsLoader( - { - slug: ctx.params.slug, - crossSelling, - count, - }, - ); - + .relatedProductsLoader({ + slug: ctx.params.slug, + crossSelling, + count, + }); return { data, status: data ? 200 : 404 }; }; - export default loaderV0; diff --git a/compat/std/functions/vtexNavbar.ts b/compat/std/functions/vtexNavbar.ts index 4766dc47d..4b74850e8 100644 --- a/compat/std/functions/vtexNavbar.ts +++ b/compat/std/functions/vtexNavbar.ts @@ -1,22 +1,19 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { Navbar } from "../../../commerce/types.ts"; import type { AppContext } from "../mod.ts"; import type { Props } from "../../../vtex/loaders/navbar.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title Navigation Bar * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - Navbar[] | null, - AppContext -> = async (_req, ctx, props) => { +const loaderV0: LoaderFunction<Props, Navbar[] | null, AppContext> = async ( + _req, + ctx, + props, +) => { const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex.navbar( props, ); - return { data, status: data ? 200 : 404 }; }; - export default loaderV0; diff --git a/compat/std/functions/vtexProductDetailsPage.ts b/compat/std/functions/vtexProductDetailsPage.ts index 373104671..f882d645e 100644 --- a/compat/std/functions/vtexProductDetailsPage.ts +++ b/compat/std/functions/vtexProductDetailsPage.ts @@ -1,26 +1,15 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { ProductDetailsPage } from "../../../commerce/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX Intelligent Search - Product Details Page (deprecated) * @description For routes of type /:slug/p * @deprecated true */ -const loaderV0: LoaderFunction< - null, - ProductDetailsPage | null, - AppContext -> = async ( - _req, - ctx, -) => { - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex - .intelligentSearch.productDetailsPage( - { slug: ctx.params.slug }, - ); - - return { data, status: data ? 200 : 404 }; -}; - +const loaderV0: LoaderFunction<null, ProductDetailsPage | null, AppContext> = + async (_req, ctx) => { + const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex + .intelligentSearch.productDetailsPage({ slug: ctx.params.slug }); + return { data, status: data ? 200 : 404 }; + }; export default loaderV0; diff --git a/compat/std/functions/vtexProductList.ts b/compat/std/functions/vtexProductList.ts index faa6739be..8917d79a2 100644 --- a/compat/std/functions/vtexProductList.ts +++ b/compat/std/functions/vtexProductList.ts @@ -1,7 +1,6 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { Product } from "../../../commerce/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; export interface Props { /** @description query to use on search */ query: string; @@ -20,7 +19,6 @@ export interface Props { | "name:asc" | "release:desc" | "discount:desc"; - // TODO: pattern property isn't being handled by RJSF /** * @title Collection ID @@ -28,31 +26,23 @@ export interface Props { */ collection?: string[]; } - /** * @title VTEX Intelligent Search - Search Products (deprecated) * @description Use it in Shelves and static Galleries. * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - Product[] | null, - AppContext -> = async ( +const loaderV0: LoaderFunction<Props, Product[] | null, AppContext> = async ( _req, ctx, props, ) => { const { query, collection, count, sort } = props; const p = query ? { query } : { collection: collection?.[0] }; - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex .intelligentSearch.productList( // deno-lint-ignore no-explicit-any { ...p, count, sort } as any, ); - return { data, status: data ? 200 : 404 }; }; - export default loaderV0; diff --git a/compat/std/functions/vtexProductListingPage.ts b/compat/std/functions/vtexProductListingPage.ts index 6e383c84f..fd1d3cebd 100644 --- a/compat/std/functions/vtexProductListingPage.ts +++ b/compat/std/functions/vtexProductListingPage.ts @@ -1,30 +1,16 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { ProductListingPage } from "../../../commerce/types.ts"; -import type { - Props, -} from "../../../vtex/loaders/intelligentSearch/productListingPage.ts"; +import type { Props } from "../../../vtex/loaders/intelligentSearch/productListingPage.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX Intelligent Search - Product Listing page (deprecated) * @description Useful for category, search, brand and collection pages. * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - ProductListingPage | null, - AppContext -> = async ( - _req, - ctx, - props, -) => { - const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex - .intelligentSearch.productListingPage( - props, - ); - - return { data, status: data ? 200 : 404 }; -}; - +const loaderV0: LoaderFunction<Props, ProductListingPage | null, AppContext> = + async (_req, ctx, props) => { + const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex + .intelligentSearch.productListingPage(props); + return { data, status: data ? 200 : 404 }; + }; export default loaderV0; diff --git a/compat/std/functions/vtexSuggestions.ts b/compat/std/functions/vtexSuggestions.ts index 01c89d924..25cfcc3de 100644 --- a/compat/std/functions/vtexSuggestions.ts +++ b/compat/std/functions/vtexSuggestions.ts @@ -1,23 +1,18 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { Suggestion } from "../../../commerce/types.ts"; import type { Props } from "../../../vtex/loaders/intelligentSearch/suggestions.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX Intelligent Search - Search Suggestions (deprecated) * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - Suggestion | null, - AppContext -> = async (_req, ctx, props) => { +const loaderV0: LoaderFunction<Props, Suggestion | null, AppContext> = async ( + _req, + ctx, + props, +) => { const data = await ctx.state.invoke["deco-sites/std"].loaders.vtex - .intelligentSearch.suggestions( - props, - ); - + .intelligentSearch.suggestions(props); return { data, status: data ? 200 : 404 }; }; - export default loaderV0; diff --git a/compat/std/functions/vtexWishlist.ts b/compat/std/functions/vtexWishlist.ts index 9799f659c..d525bab7b 100644 --- a/compat/std/functions/vtexWishlist.ts +++ b/compat/std/functions/vtexWishlist.ts @@ -1,40 +1,26 @@ -import type { LoaderFunction } from "deco/types.ts"; import type { ProductListingPage } from "../../../commerce/types.ts"; import type { Props } from "../../../vtex/loaders/wishlist.ts"; import { Product } from "../../../vtex/utils/types.ts"; import type { AppContext } from "../mod.ts"; - +import { type LoaderFunction } from "@deco/deco"; /** * @title VTEX - Load Wishlist * @description Used with vtex.wish-list app * @deprecated true */ -const loaderV0: LoaderFunction< - Props, - ProductListingPage | null, - AppContext -> = async ( - _req, - ctx, - props, -) => { - // deno-lint-ignore no-explicit-any - const data = (await ctx.state.invoke["deco-sites/std"].loaders.vtex as any) - .wishlist( - props, - ); - - return { - data: await ctx.state.invoke["deco-sites/std"].loaders.vtex - .intelligentSearch.productListingPage( - { +const loaderV0: LoaderFunction<Props, ProductListingPage | null, AppContext> = + async (_req, ctx, props) => { + // deno-lint-ignore no-explicit-any + const data = (await ctx.state.invoke["deco-sites/std"].loaders.vtex as any) + .wishlist(props); + return { + data: await ctx.state.invoke["deco-sites/std"].loaders.vtex + .intelligentSearch.productListingPage({ query: `product:${ (data as Product[]).map((p) => p.productId).join(";") }`, count: props.count, - }, - ), + }), + }; }; -}; - export default loaderV0; diff --git a/compat/std/mod.ts b/compat/std/mod.ts index 5546b3de3..773613a65 100644 --- a/compat/std/mod.ts +++ b/compat/std/mod.ts @@ -1,8 +1,5 @@ // deno-lint-ignore-file no-explicit-any export { onBeforeResolveProps } from "../../website/mod.ts"; -import { ImportMap } from "deco/blocks/app.ts"; -import { buildImportMap } from "deco/blocks/utils.tsx"; -import type { App, AppContext as AC, AppManifest } from "deco/mod.ts"; import type { PickByValue } from "https://esm.sh/utility-types@3.10.0"; import $live, { Props as LiveProps } from "../$live/mod.ts"; import commerce, { Props as CommerceProps } from "../../commerce/mod.ts"; @@ -12,15 +9,13 @@ import type { Manifest as VNDAManifest } from "../../vnda/manifest.gen.ts"; import vnda, { Props as VNDAProps } from "../../vnda/mod.ts"; import type { Manifest as VTEXManifest } from "../../vtex/manifest.gen.ts"; import vtex, { Props as VTEXProps } from "../../vtex/mod.ts"; - import type { Manifest as WebSiteManifest } from "../../website/manifest.gen.ts"; import type { ShopifyAccount, VNDAAccount, VTEXAccount } from "./deps.ts"; import type { Manifest as _Manifest } from "./manifest.gen.ts"; - import manifest from "./manifest.gen.ts"; - +import { buildImportMap, ImportMap } from "@deco/deco/blocks"; +import { type App, type AppContext as AC, type AppManifest } from "@deco/deco"; export type ManifestWithStdCompat = _Manifest; - export type ManifestMappings = Partial< { [ @@ -111,7 +106,6 @@ const manifestMappings = { "deco-sites/std/sections/configShopify.global.tsx": NOT_IMPLEMENTED, "deco-sites/std/sections/configVTEX.global.tsx": NOT_IMPLEMENTED, "deco-sites/std/sections/configYourViews.global.tsx": NOT_IMPLEMENTED, - "deco-sites/std/sections/SEO.tsx": "website/sections/Seo/Seo.tsx", }, actions: { @@ -156,7 +150,6 @@ const manifestMappings = { "vtex/actions/wishlist/removeItem.ts", }, }; - type Mappings = typeof manifestMappings; type Manifest = { [key in keyof ManifestWithStdCompat]: key extends keyof Mappings ? { @@ -172,19 +165,15 @@ type Manifest = { } : ManifestWithStdCompat[key]; }; - type AvailableCommerceProps = CommerceProps["commerce"]; - const isVTEXProps = (props: AvailableCommerceProps): props is VTEXProps => { return (props as VTEXProps)?.platform === "vtex"; }; - const isShopifyProps = ( props: AvailableCommerceProps, ): props is ShopifyProps => { return (props as ShopifyProps)?.platform === "shopify"; }; - const isVNDAProps = (props: AvailableCommerceProps): props is VNDAProps => { return (props as VNDAProps)?.platform === "vnda"; }; @@ -193,21 +182,14 @@ export type State = { configShopify?: ShopifyAccount; configVNDA?: VNDAAccount; } & AvailableCommerceProps; - export type Props = CommerceProps; - -export function WithoutCommerce( - props: LiveProps, -): App< - Manifest, - LiveProps, - [ReturnType<typeof $live>] -> { - const targetApps: Record< - string, - { manifest: AppManifest; importMap: ImportMap } - > = {}; - +export function WithoutCommerce(props: LiveProps): App<Manifest, LiveProps, [ + ReturnType<typeof $live>, +]> { + const targetApps: Record<string, { + manifest: AppManifest; + importMap: ImportMap; + }> = {}; const liveApp = $live(props); const { resolvables: _ignoreResolvables, ...webSiteApp } = liveApp.dependencies![0]; @@ -240,7 +222,6 @@ export function WithoutCommerce( } } } - return { state: props, importMap, @@ -248,37 +229,27 @@ export function WithoutCommerce( dependencies: [liveApp], }; } - export type VTEXContext = AC< - App< - Manifest, - State, - [ReturnType<typeof $live>, ReturnType<typeof vtex>] - > + App<Manifest, State, [ + ReturnType<typeof $live>, + ReturnType<typeof vtex>, + ]> >; - export type AppContext = AC<ReturnType<typeof Std>>; -export default function Std( - props: CommerceProps, -): App< - Manifest, - State, - [ReturnType<typeof $live>, ReturnType<typeof commerce>] -> { - const targetApps: Record< - string, - { manifest: AppManifest; importMap: ImportMap } - > = {}; - +export default function Std(props: CommerceProps): App<Manifest, State, [ + ReturnType<typeof $live>, + ReturnType<typeof commerce>, +]> { + const targetApps: Record<string, { + manifest: AppManifest; + importMap: ImportMap; + }> = {}; if (!props.commerce) { throw new Error( "Missing commerce props. Please migrate to apps before removing commerce prop", ); } - - const commerceApp = commerce( - props, - ); + const commerceApp = commerce(props); let state: State = { ...props.commerce }; if (isVTEXProps(props.commerce)) { state.configVTEX = { @@ -294,18 +265,15 @@ export default function Std( manifest, }; } - if (isShopifyProps(props.commerce)) { state.configShopify = props.commerce; const { manifest, state: appState } = shopify(props.commerce); state = { ...state, ...appState }; - targetApps["shopify"] = { importMap: buildImportMap(manifest), manifest, }; } - if (isVNDAProps(props.commerce)) { state.configVNDA = { domain: props.commerce.publicUrl, @@ -318,7 +286,6 @@ export default function Std( }; const { manifest, state: appState } = vnda(props.commerce); state = { ...state, ...appState }; - targetApps["vnda"] = { importMap: buildImportMap(manifest), manifest, @@ -356,9 +323,7 @@ export default function Std( } } } - const [, ecomPlatform] = commerceApp.dependencies ?? []; - return { state, importMap, diff --git a/compat/std/runtime.ts b/compat/std/runtime.ts index 8a9b18486..acfddcb66 100644 --- a/compat/std/runtime.ts +++ b/compat/std/runtime.ts @@ -1,4 +1,3 @@ -import { forApp } from "deco/clients/withManifest.ts"; import app from "./mod.ts"; - +import { forApp } from "@deco/deco/web"; export const Runtime = forApp<ReturnType<typeof app>>(); diff --git a/compat/std/sections/Analytics.tsx b/compat/std/sections/Analytics.tsx index 14faf632e..ff483f65e 100644 --- a/compat/std/sections/Analytics.tsx +++ b/compat/std/sections/Analytics.tsx @@ -1,11 +1,10 @@ -import { context } from "deco/live.ts"; import { AnalyticsEvent } from "../../../commerce/types.ts"; import { getGTMIdFromSrc, GoogleTagManager, GTAG, } from "../../../website/components/Analytics.tsx"; - +import { context } from "@deco/deco"; declare global { interface Window { DECO_ANALYTICS: Record< @@ -15,7 +14,6 @@ declare global { >; } } - /** * This function handles all ecommerce analytics events. * Add another ecommerce analytics modules here. @@ -28,43 +26,35 @@ const sendAnalyticsEvent = <T extends AnalyticsEvent>(event: T) => { event: event.name, ecommerce: event.params, }); - globalThis.window.DECO_ANALYTICS && Object.values(globalThis.window.DECO_ANALYTICS).map((f) => f("track", "ecommerce", event) ); }; - export interface Props { /** * @description google tag manager container id. For more info: https://developers.google.com/tag-platform/tag-manager/web#standard_web_page_installation . */ trackingIds?: string[]; - /** * @title GA Measurement Ids * @label measurement id * @description the google analytics property measurement id. For more info: https://support.google.com/analytics/answer/9539598 */ googleAnalyticsIds?: string[]; - /** * @description custom url for serving google tag manager. Set either this url or the tracking id */ src?: string; } - -export default function Analtyics({ - src, - trackingIds, - googleAnalyticsIds, -}: Props) { +export default function Analtyics( + { src, trackingIds, googleAnalyticsIds }: Props, +) { const isDeploy = !!context.isDeploy; // Prevent breacking change. Drop this in next major to only have // src: https://hostname // trackingId: GTM-ID const trackingId = getGTMIdFromSrc(src) ?? ""; - return ( <> {/* Add Tag Manager script during production only. To test it locally remove the condition */} diff --git a/crux/mod.ts b/crux/mod.ts index f6e068c4c..4292a68ed 100644 --- a/crux/mod.ts +++ b/crux/mod.ts @@ -1,9 +1,7 @@ import manifest, { Manifest } from "./manifest.gen.ts"; import PreviewCrux from "./preview/Preview.tsx"; -import type { App as A } from "deco/mod.ts"; - +import { type App as A } from "@deco/deco"; export type App = ReturnType<typeof CRUX>; - export interface State { /** * @title Site URL @@ -11,7 +9,6 @@ export interface State { */ siteUrl: string; } - /** * @title Chrome User Experience Report * @description Measure your site traffic at a glance in a simple and modern web analytics dashboard. @@ -24,5 +21,4 @@ export default function CRUX(state: State): A<Manifest, State> { manifest, }; } - export const Preview = PreviewCrux; diff --git a/crux/preview/Preview.tsx b/crux/preview/Preview.tsx index 49e89b264..cb5eec28c 100644 --- a/crux/preview/Preview.tsx +++ b/crux/preview/Preview.tsx @@ -1,14 +1,10 @@ -import { BaseContext } from "deco/engine/core/resolver.ts"; -import { AppRuntime } from "deco/types.ts"; import { App } from "../mod.ts"; - +import { type AppRuntime, type BaseContext } from "@deco/deco"; // this base URL was found at this documentation: https://developer.chrome.com/docs/crux/dashboard?hl=pt-br const BASE_CRUX_URL = "https://lookerstudio.google.com/embed/reporting/bbc5698d-57bb-4969-9e07-68810b9fa348/page/keDQB"; - export default function Preview(app: AppRuntime<BaseContext, App["state"]>) { const siteUrl = app.state.siteUrl; - const url = BASE_CRUX_URL + `?params=%7B"origin":"${encodeURI(siteUrl)}"%7D`; return ( <div class="h-full"> diff --git a/decohub/apps/vtex.ts b/decohub/apps/vtex.ts index d709eb624..66a661a55 100644 --- a/decohub/apps/vtex.ts +++ b/decohub/apps/vtex.ts @@ -1,9 +1,7 @@ export { default } from "../../vtex/mod.ts"; - -import { AppRuntime } from "deco/types.ts"; import { PreviewVtex } from "../../vtex/preview/Preview.tsx"; import { Markdown } from "../components/Markdown.tsx"; - +import { type AppRuntime } from "@deco/deco"; export const preview = async (props: AppRuntime) => { const markdownContent = await Markdown( new URL("../../vtex/README.md", import.meta.url).href, diff --git a/decohub/mod.ts b/decohub/mod.ts index fedb45095..3f4a2c56d 100644 --- a/decohub/mod.ts +++ b/decohub/mod.ts @@ -1,10 +1,8 @@ -import { ImportMap } from "deco/blocks/app.ts"; -import { buildImportMap } from "deco/blocks/utils.tsx"; -import { notUndefined } from "deco/engine/core/utils.ts"; -import { type App, AppModule, type FnContext } from "deco/mod.ts"; import { Markdown } from "./components/Markdown.tsx"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { buildImportMap, ImportMap } from "@deco/deco/blocks"; +import { notUndefined } from "@deco/deco/utils"; +import { type App, AppModule, type FnContext } from "@deco/deco"; /** * @title App */ @@ -13,14 +11,11 @@ export interface DynamicApp { name: string; importMap?: ImportMap; } - export interface State { enableAdmin?: boolean; apps: DynamicApp[]; } - const DENY_DYNAMIC_IMPORT = Deno.env.get("DENY_DYNAMIC_IMPORT") === "true"; - /** * @title Deco Hub * @description Unlock apps and integrations on deco.cx @@ -29,16 +24,19 @@ const DENY_DYNAMIC_IMPORT = Deno.env.get("DENY_DYNAMIC_IMPORT") === "true"; */ const ADMIN_APP = "decohub/apps/admin.ts"; const FILES_APP = "decohub/apps/files.ts"; -export default async function App( - state: State, -): Promise<App<Manifest, State>> { +export default async function App(state: State): Promise<App<Manifest, State>> { const resolvedAdminImport = import.meta.resolve("../admin/mod.ts"); const resolvedFilesImport = import.meta.resolve("../files/mod.ts"); const baseImportMap = buildImportMap(manifest); const appModules = DENY_DYNAMIC_IMPORT ? [] : await Promise.all( (state?.apps ?? []).filter(Boolean).map(async (app) => { const appMod = await import(app.importUrl).catch((err) => { - console.error("error when importing app", app.name, app.importUrl, err); + console.error( + "error when importing app", + app.name, + app.importUrl, + err, + ); return null; }); if (!appMod) { @@ -53,25 +51,22 @@ export default async function App( }), ); const [dynamicApps, enhancedImportMap] = appModules.filter(notUndefined) - .reduce( - ([apps, importmap], app) => { - const appTs = `${app.name}.ts`; - const appName = `${manifest.name}/apps/${appTs}`; - return [{ - ...apps, - [appName]: app.module, - }, { - ...importmap, - ...app.importMap ?? {}, - imports: { - ...importmap?.imports ?? {}, - ...app.importMap?.imports ?? {}, - [appName]: app.importUrl, - }, - }]; - }, - [{} as Record<string, AppModule>, baseImportMap], - ); + .reduce(([apps, importmap], app) => { + const appTs = `${app.name}.ts`; + const appName = `${manifest.name}/apps/${appTs}`; + return [{ + ...apps, + [appName]: app.module, + }, { + ...importmap, + ...app.importMap ?? {}, + imports: { + ...importmap?.imports ?? {}, + ...app.importMap?.imports ?? {}, + [appName]: app.importUrl, + }, + }]; + }, [{} as Record<string, AppModule>, baseImportMap]); return { manifest: { ...manifest, @@ -81,12 +76,8 @@ export default async function App( ...manifest.apps, ...state.enableAdmin // this is an optimization to not include the admin code for everyone in case of play is not being used. ? { - [ADMIN_APP]: await import( - resolvedAdminImport - ), - [FILES_APP]: await import( - resolvedFilesImport - ), + [ADMIN_APP]: await import(resolvedAdminImport), + [FILES_APP]: await import(resolvedFilesImport), } : {}, }, @@ -108,9 +99,7 @@ export default async function App( }, }; } - export type AppContext = FnContext<State, Manifest>; - export const Preview = await Markdown( new URL("./README.md", import.meta.url).href, ); diff --git a/deno.json b/deno.json index eae4cc3f8..a3fd0be7b 100644 --- a/deno.json +++ b/deno.json @@ -1,16 +1,23 @@ { "imports": { "$fresh/": "https://denopkg.com/denoland/fresh@1.6.8/", + "@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.5", + "@core/asyncutil": "jsr:@core/asyncutil@^1.0.2", + "@deco/codemod-toolkit": "jsr:@deco/codemod-toolkit@^0.2.1", + "@deco/durable": "jsr:@deco/durable@^0.5.3", + "@deco/warp": "jsr:@deco/warp@0.3.6", + "@hono/hono": "jsr:@hono/hono@^4.5.4", "preact": "npm:preact@10.23.1", - "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.0", + "preact-render-to-string": "npm:preact-render-to-string@6.4.0", "@preact/signals": "https://esm.sh/*@preact/signals@1.1.3", "@preact/signals-core": "https://esm.sh/@preact/signals-core@1.3.0", "std/": "https://deno.land/std@0.204.0/", "partytown/": "https://deno.land/x/partytown@0.4.8/", "deco-sites/std/": "https://denopkg.com/deco-sites/std@1.26.8/", - "deco/": "https://denopkg.com/deco-cx/deco@1.95.2/", + "deco/": "https://denopkg.com/deco-cx/deco@1.97.5/", "@std/assert": "jsr:@std/assert@^1.0.2", "@std/async": "jsr:@std/async@^0.224.1", + "@std/cli": "jsr:@std/cli@^1.0.3", "@std/crypto": "jsr:@std/crypto@1.0.0-rc.1", "@std/datetime": "jsr:@std/datetime@^0.224.0", "@std/encoding": "jsr:@std/encoding@^1.0.0-rc.1", @@ -25,21 +32,18 @@ "@std/semver": "jsr:@std/semver@^0.224.3", "@std/streams": "jsr:@std/streams@^1.0.0", "@std/testing": "jsr:@std/testing@^1.0.0", - "@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.5", - "@core/asyncutil": "jsr:@core/asyncutil@^1.0.2", - "@deco/durable": "jsr:@deco/durable@^0.5.3", - "@deco/warp": "jsr:@deco/warp@0.3.6", - "@hono/hono": "jsr:@hono/hono@^4.5.4", - "@std/cli": "jsr:@std/cli@^1.0.3", "@zaubrik/djwt": "jsr:@zaubrik/djwt@^3.0.2", "fast-json-patch": "npm:fast-json-patch@^3.1.1", - "simple-git": "npm:simple-git@^3.25.0" + "simple-git": "npm:simple-git@^3.25.0", + "https://esm.sh/*preact-render-to-string@6.3.1": "npm:preact-render-to-string@6.3.1", + "@deco/deco": "jsr:@deco/deco@^1.98.0", + "@deco/inspect-vscode": "jsr:@deco/inspect-vscode@^0.2.1" }, "lock": false, "tasks": { "check": "deno fmt && deno lint && deno check **/mod.ts", "release": "deno eval 'import \"deco/scripts/release.ts\"'", - "start": "deno run -A ./scripts/start.ts", + "start": "deno run -A --unstable-http --env https://deco.cx/run -- deno task dev", "bundle": "deno eval 'import \"deco/scripts/apps/bundle.ts\"'", "link": "deno eval 'import \"deco/scripts/apps/link.ts\"'", "unlink": "deno eval 'import \"deco/scripts/apps/unlink.ts\"'", diff --git a/emailjs/mod.ts b/emailjs/mod.ts index 12153ac33..ff52e2ac0 100644 --- a/emailjs/mod.ts +++ b/emailjs/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { fetchSafe } from "../utils/fetch.ts"; import { createHttpClient } from "../utils/http.ts"; @@ -6,7 +5,7 @@ import { PreviewContainer } from "../utils/preview.tsx"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import type { EmailJSApi } from "./utils/client.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface Props { /** * @description Service ID of the service through which the email should be sent. Reserved keyword default_service is supported, and should be used to use the default service, which can be set and changed via EmailJS dashboard. @@ -15,22 +14,16 @@ export interface Props { user_id: string; accessToken: Secret; } - export interface State extends Props { api: ReturnType<typeof createHttpClient<EmailJSApi>>; } - /** * @title EmailJS * @description EmailJS integrates easily with popular email services like Gmail and Outlook, offering features to enhance email functionality. * @logo https://raw.githubusercontent.com/deco-cx/apps/main/emailjs/logo.png */ export default function App( - { - accessToken, - service_id, - user_id, - }: State, + { accessToken, service_id, user_id }: State, ): App<Manifest, State> { const api = createHttpClient<EmailJSApi>({ base: "https://api.emailjs.com/api/v1.0", @@ -40,29 +33,23 @@ export default function App( "Accept": "application/json", }), }); - const state = { accessToken, service_id, user_id, api, }; - const app: App<Manifest, typeof state> = { state, manifest, }; - return app; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/files/loaders/app.ts b/files/loaders/app.ts index f6f44ba0e..3e02b5c2f 100644 --- a/files/loaders/app.ts +++ b/files/loaders/app.ts @@ -1,5 +1,5 @@ -import { ImportMap } from "deco/blocks/app.ts"; -import { decoManifestBuilder } from "deco/engine/manifest/manifestGen.ts"; +import { ImportMap } from "@deco/deco/blocks"; +import { decoManifestBuilder } from "@deco/deco/utils"; import { createCache } from "https://deno.land/x/deno_cache@0.6.3/mod.ts"; import { build, initialize } from "https://deno.land/x/esbuild@v0.20.2/wasm.js"; import { @@ -10,23 +10,16 @@ import { dirname, join } from "std/path/mod.ts"; import { DynamicApp } from "../../decohub/mod.ts"; import { AppContext } from "../mod.ts"; import { create, FileSystemNode, isDir, nodesToMap, walk } from "../sdk.ts"; - const initializePromise = initialize({ wasmURL: "https://deno.land/x/esbuild@v0.20.2/esbuild.wasm", worker: false, }); - const decoder = new TextDecoder(); let cache: ReturnType<typeof createCache> | null = null; - -async function bundle( - contents: string, - importMap: ImportMap, -): Promise<string> { +async function bundle(contents: string, importMap: ImportMap): Promise<string> { await initializePromise; const currdirUrl = new URL(currdir); const resolvedImportMap = resolveImportMap(importMap, currdirUrl); - const { outputFiles } = await build({ stdin: { contents, @@ -73,19 +66,19 @@ async function bundle( cache ??= createCache(); return { loader: "tsx", - contents: await cache.load(specifier).then( - (cached) => { - const content = (cached as { content: string | Uint8Array }) - ?.content; - if (!content) { - return specifier; - } - if (typeof content === "string") { - return content; - } - return decoder.decode(content); - }, - ), + contents: await cache.load(specifier).then((cached) => { + const content = (cached as { + content: string | Uint8Array; + }) + ?.content; + if (!content) { + return specifier; + } + if (typeof content === "string") { + return content; + } + return decoder.decode(content); + }), }; }, ); @@ -93,12 +86,9 @@ async function bundle( }, ], }); - return outputFiles[0].text; } - const currdir = dirname(import.meta.url); - export const contentToDataUri = ( path: string, modData: string, @@ -109,28 +99,19 @@ export const contentToDataUri = ( }`; export const contentToJSONDataUri = (path: string, modData: string) => contentToDataUri(path, modData, "application/json"); - export interface TsContent { path: string; content: string; } - const buildImportMap = (root: FileSystemNode): ImportMap => { const importMap: ImportMap = { imports: {} }; - for (const { path, content } of walk(root)) { if (!/\.tsx?$/.test(path)) { continue; } - - const dataUri = contentToDataUri( - join(currdir, path), - content, - ); - + const dataUri = contentToDataUri(join(currdir, path), content); importMap.imports[path] = dataUri; } - return importMap; }; const includeRelative = (app: string, importMap: ImportMap): ImportMap => { @@ -145,14 +126,12 @@ const includeRelative = (app: string, importMap: ImportMap): ImportMap => { export interface Props { name: string; } - const loader = async ( props: Props, _req: Request, ctx: AppContext, ): Promise<DynamicApp> => { const { root = create() } = ctx; - if (!isDir(root)) { throw new Error("root should be a directory"); } @@ -160,49 +139,38 @@ const loader = async ( if (!appFolder) { throw new Error(`app ${props.name} not found`); } - if (!isDir(appFolder)) { throw new Error("root should be a directory"); } - const importMap = buildImportMap(appFolder); importMap.imports[`${props.name}/`] = `./`; importMap.imports["./"] = currdir; const nodeMap = nodesToMap(appFolder.nodes); - const manifestString = (await decoManifestBuilder("", props.name, async function* (dir) { const fs = nodeMap[dir]; - if (!fs) { return; } - for await (const file of walk(fs)) { yield file; } })).build(); - const manifestImport = contentToDataUri( join(currdir, props.name, "manifest.gen.ts"), manifestString, ); - const manifestImportStr = `${props.name}/manifest.gen.ts`; - importMap.imports[manifestImportStr] = manifestImport; - const withRelativeImports = includeRelative(props.name, importMap); - const modTs = appFolder.nodes.find((node) => node.name === "mod.ts"); - let modTsContent = modTs && !isDir(modTs) ? modTs.content : undefined; if (!modTsContent) { modTsContent = ` \n import manifest, { Manifest } from "./manifest.gen.ts"; import website, { Props as WebSiteProps } from "apps/website/mod.ts"; -import { App, AppContext as AC } from "deco/mod.ts"; +import { App, AppContext as AC } from "@deco/deco"; export default function App(props: WebSiteProps): App<Manifest, WebSiteProps, [ReturnType<typeof website>]> { return { @@ -216,7 +184,6 @@ export type AppContext = AC<ReturnType<typeof App>>; \n `; } - return { name: props.name, importUrl: contentToDataUri( @@ -226,5 +193,4 @@ export type AppContext = AC<ReturnType<typeof App>>; importMap, }; }; - export default loader; diff --git a/files/mod.ts b/files/mod.ts index ac506a3d2..bdec2c835 100644 --- a/files/mod.ts +++ b/files/mod.ts @@ -1,21 +1,16 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { FileSystemNode } from "./sdk.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface State { /** * @title File System */ root?: FileSystemNode; } - /** * @title My Workspace */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof App>>; diff --git a/htmx/mod.ts b/htmx/mod.ts index 0ffe1c6f1..0919997db 100644 --- a/htmx/mod.ts +++ b/htmx/mod.ts @@ -1,10 +1,8 @@ -import { App, FnContext } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext<Props, Manifest>; - export type Extension = | "ajax-header" | "alpine-morph" @@ -28,18 +26,14 @@ export type Extension = | "ws" | "path-params" | "sse"; - export interface Props { /** @default 1.9.11 */ version?: string; - /** @defaul https://cdn.jsdelivr.net/npm */ cdn?: string; - /** @title HTMX extensions to include */ extensions?: Extension[]; } - /** * @title HTMX * @description high power tools for HTML. @@ -56,12 +50,10 @@ export default function Site(state: Props): App<Manifest, Required<Props>> { manifest, }; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/htmx/sections/Deferred.tsx b/htmx/sections/Deferred.tsx index 1ff504fbb..9c4f7a392 100644 --- a/htmx/sections/Deferred.tsx +++ b/htmx/sections/Deferred.tsx @@ -1,9 +1,8 @@ -import type { Section } from "deco/blocks/section.ts"; -import { useSection } from "deco/hooks/useSection.ts"; -import { asResolved, isDeferred } from "deco/mod.ts"; import { shouldForceRender } from "../../utils/deferred.ts"; import { AppContext } from "../mod.ts"; - +import { type Section } from "@deco/deco/blocks"; +import { useSection } from "@deco/deco/hooks"; +import { asResolved, isDeferred } from "@deco/deco"; /** * @titleBy type * @description fires once when the element is first loaded @@ -13,7 +12,6 @@ interface Load { /** @hide true */ delay?: number; } - /** * @titleBy type * @description fires once when an element first scrolls into the viewport @@ -21,7 +19,6 @@ interface Load { interface Revealed { type: "revealed"; } - /** * @titleBy type * @description fires once when an element first intersects the viewport. @@ -31,7 +28,6 @@ interface Intersect { /** @description a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire the event on */ threshold?: number; } - export interface Props { sections: Section[]; /** @hide true */ @@ -39,10 +35,8 @@ export interface Props { trigger?: Load | Revealed | Intersect; loading?: "lazy" | "eager"; } - const Deferred = (props: Props) => { const { sections, loading, trigger } = props; - if (loading === "eager") { return ( <> @@ -50,16 +44,13 @@ const Deferred = (props: Props) => { </> ); } - const href = useSection<typeof Deferred>({ props: { loading: "eager" }, }); - const triggerList: (string | number)[] = [trigger?.type ?? "load", "once"]; if (trigger?.type === "load" && trigger.delay !== undefined) { triggerList.push(`delay:${trigger.delay}ms`); } - return ( <> <div @@ -75,7 +66,6 @@ const Deferred = (props: Props) => { </> ); }; - export const loader = async (props: Props, req: Request, ctx: AppContext) => { const url = new URL(req.url); const shouldRender = props.loading === "eager" || @@ -86,12 +76,9 @@ export const loader = async (props: Props, req: Request, ctx: AppContext) => { : props.sections; return { ...props, sections, loading: "eager" }; } - return { ...props, sections: [] }; }; - const DEFERRED = true; - export const onBeforeResolveProps = (props: Props) => { return { ...props, @@ -99,5 +86,4 @@ export const onBeforeResolveProps = (props: Props) => { sections: asResolved(props.sections, DEFERRED), }; }; - export default Deferred; diff --git a/htmx/sections/htmx.tsx b/htmx/sections/htmx.tsx index 629810c39..5a8d4f7b0 100644 --- a/htmx/sections/htmx.tsx +++ b/htmx/sections/htmx.tsx @@ -1,21 +1,18 @@ import { Head } from "$fresh/runtime.ts"; -import { SectionProps } from "deco/mod.ts"; -import { useScript } from "deco/hooks/useScript.ts"; import { AppContext, Extension } from "../mod.ts"; - +import { type SectionProps } from "@deco/deco"; +import { useScript } from "@deco/deco/hooks"; const script = (extensions: Extension[]) => { if (extensions.length > 0) { if (document.readyState === "complete") { document.body.setAttribute("hx-ext", extensions.join(",")); return; } - globalThis.onload = () => { document.body.setAttribute("hx-ext", extensions.join(",")); }; } }; - function Section({ version, cdn, extensions }: SectionProps<typeof loader>) { return ( <Head> @@ -37,9 +34,7 @@ function Section({ version, cdn, extensions }: SectionProps<typeof loader>) { </Head> ); } - export const loader = (_props: unknown, _req: Request, ctx: AppContext) => { return { version: ctx.version!, cdn: ctx.cdn!, extensions: ctx.extensions! }; }; - export default Section; diff --git a/implementation/mod.ts b/implementation/mod.ts index f55c26089..b5b00f5fc 100644 --- a/implementation/mod.ts +++ b/implementation/mod.ts @@ -1,8 +1,7 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type Agency = | "2B Digital" | "Adaptio" @@ -39,7 +38,6 @@ export type Agency = | "Wecode" | "Wedigi" | "Wicomm"; - export interface State { serviceProvider?: Agency; /** @@ -47,26 +45,20 @@ export interface State { */ expectedGoLive?: string; } - /** * @title Implementer * @description The agency that's implementing your store * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/konfidency/loaders/productDetailsPage.ts b/konfidency/loaders/productDetailsPage.ts index 0806b256e..1fa179177 100644 --- a/konfidency/loaders/productDetailsPage.ts +++ b/konfidency/loaders/productDetailsPage.ts @@ -1,9 +1,8 @@ -import { logger } from "deco/observability/mod.ts"; import { ProductDetailsPage } from "../../commerce/types.ts"; import { ExtensionOf } from "../../website/loaders/extension.ts"; import { AppContext } from "../mod.ts"; import { toReview } from "../utils/transform.ts"; - +import { logger } from "@deco/deco/o11y"; export interface Props { /** * @description The default value is 5 @@ -14,7 +13,6 @@ export interface Props { */ page?: number; } - export default function productDetailsPage( { pageSize = 5, page = 1 }: Props, _req: Request, @@ -25,7 +23,6 @@ export default function productDetailsPage( if (!productDetailsPage) { return null; } - try { const reviews = await api["GET /:customer/:sku/summary"]({ customer, @@ -34,7 +31,6 @@ export default function productDetailsPage( sku: productDetailsPage.product.inProductGroupWithID as string, }).then((res) => res.json()); const { aggregateRating, review } = toReview(reviews.reviews[0]); - return { ...productDetailsPage, product: { diff --git a/konfidency/mod.ts b/konfidency/mod.ts index 057ecd381..335ebda29 100644 --- a/konfidency/mod.ts +++ b/konfidency/mod.ts @@ -1,37 +1,29 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { createHttpClient } from "../utils/http.ts"; import { PreviewContainer } from "../utils/preview.tsx"; import manifest, { Manifest } from "./manifest.gen.ts"; import type { API } from "./utils/client.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface Props { customer: string; } - export interface State extends Props { api: ReturnType<typeof createHttpClient<API>>; } /** * @title konfidency */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { const api = createHttpClient<API>({ base: `https://reviews-api.konfidency.com.br`, }); - return { manifest, state: { ...state, api } }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/linx-impulse/mod.ts b/linx-impulse/mod.ts index e09e95839..d033260e2 100644 --- a/linx-impulse/mod.ts +++ b/linx-impulse/mod.ts @@ -1,8 +1,3 @@ -import type { - App as A, - AppContext as AC, - AppMiddlewareContext as AMC, -} from "deco/mod.ts"; import { createHttpClient } from "../utils/http.ts"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; @@ -11,11 +6,14 @@ import { ChaordicAPI } from "./utils/chaordic.ts"; import { LinxAPI } from "./utils/client.ts"; import { EventsAPI } from "./utils/events.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { + type App as A, + type AppContext as AC, + type AppMiddlewareContext as AMC, +} from "@deco/deco"; export type App = ReturnType<typeof Linx>; export type AppContext = AC<App>; export type AppMiddlewareContext = AMC<App>; - /** @title LINX Impulse */ export interface State { /** @@ -39,9 +37,7 @@ export interface State { salesChannel?: string; enableMobileSource?: boolean; } - export const color = 0xFF6A3B; - /** * @title Linx Impulse * @description Build, manage and deliver B2B, B2C and Marketplace commerce experiences. @@ -54,26 +50,19 @@ export default function Linx({ secretKey, ...props }: State) { "x-secret-key", typeof secretKey === "string" ? secretKey : secretKey?.get() ?? "", ); - props.origin && headers.set( - "origin", - props.origin, - ); - + props.origin && headers.set("origin", props.origin); const eventsApi = createHttpClient<EventsAPI>({ base: "https://api.event.linximpulse.net/", headers, }); - const api = createHttpClient<LinxAPI>({ base: "http://api.linximpulse.com/", headers, }); - const chaordicApi = createHttpClient<ChaordicAPI>({ base: "https://recs.chaordicsystems.com/", headers, }); - const state = { ...props, eventsApi, @@ -81,12 +70,9 @@ export default function Linx({ secretKey, ...props }: State) { chaordicApi, cdn: props.cdn?.replace(/\/$/, "") ?? "", // remove trailing slash }; - const app: A<Manifest, typeof state> = { manifest, state, middleware }; - return app; } - export const preview = () => { return { Component: PreviewContainer, diff --git a/linx-impulse/runtime.ts b/linx-impulse/runtime.ts index 41d65a98d..da42a2435 100644 --- a/linx-impulse/runtime.ts +++ b/linx-impulse/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/linx-impulse/sections/Analytics/LinxImpulsePageView.tsx b/linx-impulse/sections/Analytics/LinxImpulsePageView.tsx index 0f69f8460..5ef4fb944 100644 --- a/linx-impulse/sections/Analytics/LinxImpulsePageView.tsx +++ b/linx-impulse/sections/Analytics/LinxImpulsePageView.tsx @@ -1,16 +1,15 @@ -import { SectionProps } from "deco/types.ts"; import { PageInfo, Person, ProductDetailsPage, ProductListingPage, } from "../../../commerce/types.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import { AppContext } from "../../mod.ts"; import getSource from "../../utils/source.ts"; import type { LinxUser } from "../../utils/types/analytics.ts"; import { getDeviceIdFromBag } from "../../utils/deviceId.ts"; - +import { type SectionProps } from "@deco/deco"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; type Page = | "home" | "category" @@ -23,7 +22,6 @@ type Page = | "hotsite" | "userprofile" | "other"; - interface Category { /** * @hide @@ -32,7 +30,6 @@ interface Category { page: string; products: ProductListingPage | null; } - interface Subcategory { /** * @hide @@ -41,7 +38,6 @@ interface Subcategory { page: string; products: ProductListingPage | null; } - interface Product { /** * @hide @@ -50,7 +46,6 @@ interface Product { page: string; details: ProductDetailsPage | null; } - interface Home { /** * @hide @@ -58,7 +53,6 @@ interface Home { */ page: string; } - interface Other { /** * @hide @@ -66,7 +60,6 @@ interface Other { */ page: string; } - interface Search { /** * @hide @@ -75,7 +68,6 @@ interface Search { page: string; result: ProductListingPage | null; } - interface Checkout { /** * @hide @@ -83,7 +75,6 @@ interface Checkout { */ page: string; } - interface LandingPage { /** * @hide @@ -91,7 +82,6 @@ interface LandingPage { */ page: string; } - interface NotFound { /** * @hide @@ -99,7 +89,6 @@ interface NotFound { */ page: string; } - interface Hotsite { /** * @hide @@ -107,7 +96,6 @@ interface Hotsite { */ page: string; } - interface UserProfile { /** * @hide @@ -115,13 +103,11 @@ interface UserProfile { */ page: string; } - interface SendViewEventParams { page: Page | string; // deno-lint-ignore no-explicit-any body?: Record<string, any>; } - interface Props { /** * @title Event @@ -141,14 +127,13 @@ interface Props { | UserProfile; user: Person | null; } - /** @title Linx Impulse Integration - Events */ export const script = async (props: SectionProps<typeof loader>) => { const { event, source, apiKey, salesChannel, url: urlStr, deviceId } = props; - if (!event) return; - + if (!event) { + return; + } const { page } = event; - const user: LinxUser | undefined = props.user ? { id: props.user["@id"] ?? props.user.email ?? "", @@ -160,18 +145,14 @@ export const script = async (props: SectionProps<typeof loader>) => { birthday: undefined, } : undefined; - const sendViewEvent = (params: SendViewEventParams) => { const baseUrl = new URL( `https://api.event.linximpulse.net/v7/events/views/${params.page}`, ); - // deviceId && baseUrl.searchParams.append("deviceId", deviceId); - const headers = new Headers(); headers.set("content-type", "application/json"); props.origin && headers.set("origin", props.origin); - return fetch(baseUrl.toString(), { method: "POST", credentials: "include", @@ -186,54 +167,46 @@ export const script = async (props: SectionProps<typeof loader>) => { }), }); }; - const getSearchIdFromPageInfo = (pageInfo?: PageInfo | null) => { const searchIdInPageTypes = pageInfo?.pageTypes?.find((pageType) => pageType?.startsWith("SearchId:") ); return searchIdInPageTypes?.replace("SearchId:", ""); }; - const getCategoriesFromPage = (page: ProductListingPage) => { if (page.breadcrumb.itemListElement.length) { return page.breadcrumb.itemListElement.map((item) => item.name!); } else { - const departmentsFilter = page.filters.find( - (filter) => filter.key === "Departments", + const departmentsFilter = page.filters.find((filter) => + filter.key === "Departments" )?.values; if (Array.isArray(departmentsFilter)) { return departmentsFilter.map((value) => value.label); } } }; - const getItemsFromProducts = (products?: ProductListingPage["products"]) => { - return ( - products?.map((product) => { - return { - pid: product.isVariantOf?.productGroupID ?? product.productID, - sku: product.sku, - }; - }) ?? [] - ); + return (products?.map((product) => { + return { + pid: product.isVariantOf?.productGroupID ?? product.productID, + sku: product.sku, + }; + }) ?? []); }; - const url = new URL(urlStr); - switch (page) { case "category": { let searchId: string | undefined; let categories: string[] | undefined; - if ("products" in event && event.products) { const query = url.searchParams.get("q"); - const searchIndex = event.products.pageInfo.pageTypes?.findIndex( - (pageType) => pageType === "Search", - ) ?? -1; + const searchIndex = + event.products.pageInfo.pageTypes?.findIndex((pageType) => + pageType === "Search" + ) ?? -1; // If page has a search term if (searchIndex > 0 || query) { const items = getItemsFromProducts(event.products.products); - await sendViewEvent({ page: "search", body: { @@ -242,14 +215,11 @@ export const script = async (props: SectionProps<typeof loader>) => { searchId, }, }); - break; } - searchId = getSearchIdFromPageInfo(event.products.pageInfo); categories = getCategoriesFromPage(event.products); } - await sendViewEvent({ page: (categories?.length ?? 1) === 1 ? "category" : "subcategory", body: { @@ -257,18 +227,15 @@ export const script = async (props: SectionProps<typeof loader>) => { searchId, }, }); - break; } case "subcategory": { let searchId: string | undefined; let categories: string[] | undefined; - if ("products" in event && event.products) { searchId = getSearchIdFromPageInfo(event.products.pageInfo); categories = getCategoriesFromPage(event.products); } - await sendViewEvent({ page: "subcategory", body: { @@ -276,13 +243,13 @@ export const script = async (props: SectionProps<typeof loader>) => { searchId, }, }); - break; } case "product": { - if (!("details" in event) || !event.details) break; + if (!("details" in event) || !event.details) { + break; + } const { details } = event; - await sendViewEvent({ page, body: { @@ -292,7 +259,6 @@ export const script = async (props: SectionProps<typeof loader>) => { sku: details.product.sku, }, }); - break; } case "search": { @@ -301,7 +267,6 @@ export const script = async (props: SectionProps<typeof loader>) => { ); const query = url.searchParams.get("q") ?? url.pathname.split("/").pop() ?? ""; - if ( !("result" in event) || !event.result || @@ -316,11 +281,8 @@ export const script = async (props: SectionProps<typeof loader>) => { }, }); } - const { result } = event; - const items = getItemsFromProducts(result.products); - await sendViewEvent({ page, body: { @@ -329,34 +291,28 @@ export const script = async (props: SectionProps<typeof loader>) => { searchId, }, }); - break; } - case "landingpage": { await sendViewEvent({ page: "landing_page", }); - break; } case "notfound": { await sendViewEvent({ page: "not_found", }); - break; } default: { await sendViewEvent({ page, }); - break; } } }; - /** @title Linx Impulse - Page View Events */ export const loader = (props: Props, req: Request, ctx: AppContext) => ({ ...props, @@ -367,7 +323,6 @@ export const loader = (props: Props, req: Request, ctx: AppContext) => ({ deviceId: getDeviceIdFromBag(ctx), origin: ctx.origin, }); - export default function LinxImpulsePageView( props: SectionProps<typeof loader>, ) { diff --git a/linx-impulse/sections/Script/LinxImpulseScript.tsx b/linx-impulse/sections/Script/LinxImpulseScript.tsx index c03980019..012b73ed5 100644 --- a/linx-impulse/sections/Script/LinxImpulseScript.tsx +++ b/linx-impulse/sections/Script/LinxImpulseScript.tsx @@ -1,11 +1,9 @@ import { Head } from "$fresh/runtime.ts"; -import { SectionProps } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; - +import { type SectionProps } from "@deco/deco"; export const loader = (_props: unknown, _req: Request, ctx: AppContext) => ({ apiKey: ctx.apiKey, }); - export default function LinxImpulseScript( { apiKey }: SectionProps<ReturnType<typeof loader>>, ) { diff --git a/linx/loaders/page.ts b/linx/loaders/page.ts index 5871e7725..a494240ee 100644 --- a/linx/loaders/page.ts +++ b/linx/loaders/page.ts @@ -1,12 +1,10 @@ -import type { Page } from "deco/blocks/page.tsx"; -import { asResolved, isDeferred } from "deco/mod.ts"; import type { AppContext } from "../mod.ts"; import { LinxPage } from "./pages.ts"; - +import { type Page } from "@deco/deco/blocks"; +import { asResolved, isDeferred } from "@deco/deco"; interface Props { pages: LinxPage[]; } - /** * @title LINX Integration * @description Load Page as JSON @@ -18,12 +16,9 @@ const loader = async ( ): Promise<Page | null> => { const response = await ctx.invoke("linx/loaders/path.ts"); const [, type] = response?.PageInfo.RouteClass.split("-") ?? []; - const match = props.pages?.find(({ selected }) => selected === type); - return isDeferred<Page>(match?.page) ? match?.page() ?? null : null; }; - export const onBeforeResolveProps = (props: Props) => ({ ...props, pages: props?.pages.map((linx) => ({ @@ -31,5 +26,4 @@ export const onBeforeResolveProps = (props: Props) => ({ page: asResolved(linx?.page, true), })), }); - export default loader; diff --git a/linx/loaders/pages.ts b/linx/loaders/pages.ts index 7486a183d..31cab58d9 100644 --- a/linx/loaders/pages.ts +++ b/linx/loaders/pages.ts @@ -1,18 +1,15 @@ -import type { Page } from "deco/blocks/page.tsx"; -import { asResolved } from "deco/mod.ts"; import type { Route } from "../../website/flags/audience.ts"; import type { AppContext } from "../mod.ts"; - +import { type Page } from "@deco/deco/blocks"; +import { asResolved } from "@deco/deco"; /** @titleBy selected */ export interface LinxPage { selected: "category" | "search" | "product"; page: Page; } - interface Props { pages: LinxPage[]; } - const PROXY_PATHS = [ "/carrinho", "/carrinho/*", @@ -30,7 +27,6 @@ const PROXY_PATHS = [ "/painel-do-cliente", "/painel-do-cliente/*", ]; - /** * @title Linx Pages */ @@ -57,7 +53,6 @@ const loader = (props: Props, _req: Request, ctx: AppContext): Route[] => [ }, })), ]; - export const onBeforeResolveProps = (props: Props) => ({ ...props, pages: props?.pages.map((linx) => ({ @@ -65,5 +60,4 @@ export const onBeforeResolveProps = (props: Props) => ({ page: asResolved(linx?.page), })), }); - export default loader; diff --git a/linx/loaders/path.ts b/linx/loaders/path.ts index 996979d80..9d29c96fa 100644 --- a/linx/loaders/path.ts +++ b/linx/loaders/path.ts @@ -1,8 +1,7 @@ -import { redirect } from "deco/mod.ts"; import { STALE } from "../../utils/fetch.ts"; import type { AppContext } from "../mod.ts"; import type { API } from "../utils/client.ts"; - +import { redirect } from "@deco/deco"; /** * @title LINX Integration * @description Load Page as JSON @@ -31,7 +30,6 @@ async function loader( params[key] = params[key] || []; params[key].push(value); }); - /** * TODO: Fix the /*splat route being called for images and other assets. */ @@ -39,15 +37,11 @@ async function loader( console.error("imagem"); return null; } - const splat = `${upstream.pathname.slice(1)}.json`; - const defaults = { fc: params.fc || props.fc || "false", }; - const isSearch = upstream.searchParams.has("t"); - const response = await ctx.api["GET /*splat"]( { splat, ...params, ...props, ...defaults }, isSearch @@ -56,19 +50,14 @@ async function loader( } : STALE, ); - if (response.status === 301) { const redirectUrlRaw = response.headers.get("location"); - if (!redirectUrlRaw) { return null; } - const redirectUrl = new URL(redirectUrlRaw); throw redirect(redirectUrl); } - return response.json(); } - export default loader; diff --git a/linx/mod.ts b/linx/mod.ts index 0ac024b22..29bf54d01 100644 --- a/linx/mod.ts +++ b/linx/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { createHttpClient } from "../utils/http.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { API } from "./utils/client.ts"; @@ -6,9 +5,8 @@ import { Secret } from "../website/loaders/secret.ts"; import { LayerAPI } from "./utils/layer.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC<ReturnType<typeof App>>; - /** @title LINX */ export interface State { /** @@ -16,36 +14,29 @@ export interface State { * @default deco */ account: string; - /** * @title Image CDN URL * @description e.g.: https://{account}.cloudfront.net/ */ cdn: string; - /** * @title Linx integration token * @description user:password of a Linx integration account encoded in base64 */ integrationToken: Secret; } - export const color = 0xFF6A3B; - /** * IMPORTANT: This app needs the DECO_PROXY_DOMAIN=linx.decocache.com * environment variable to work properly. */ - /** * @title Linx * @description Loaders, actions and workflows for adding Linx Commerce Platform to your website. * @category Ecommmerce * @logo https://raw.githubusercontent.com/deco-cx/apps/main/linx/logo.png */ -export default function App( - { account, cdn, integrationToken }: State, -) { +export default function App({ account, cdn, integrationToken }: State) { const token = integrationToken.get(); const headers = new Headers({ "Accept": "application/json", @@ -54,29 +45,22 @@ export default function App( "Authorization": `Basic ${token}`, "x-external-service": "deco", }); - const api = createHttpClient<API>({ base: `https://${account}.core.dcg.com.br/`, headers, }); - const layer = createHttpClient<LayerAPI>({ base: `https://${account}.layer.core.dcg.com.br/`, headers, }); - const state = { cdn, api, layer, account }; - const app: App<Manifest, typeof state> = { manifest, state }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/linx/runtime.ts b/linx/runtime.ts index 41d65a98d..da42a2435 100644 --- a/linx/runtime.ts +++ b/linx/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/mailchimp/actions/subscribe.ts b/mailchimp/actions/subscribe.ts index 5b7b98c0b..0d9b0bbc2 100644 --- a/mailchimp/actions/subscribe.ts +++ b/mailchimp/actions/subscribe.ts @@ -1,4 +1,4 @@ -import { setCookie } from "deco/deps.ts"; +import { setCookie } from "std/http/cookie.ts"; import { AppContext } from "../mod.ts"; import { toMd5 } from "../utils/transform.ts"; diff --git a/mailchimp/loaders/options/lists.ts b/mailchimp/loaders/options/lists.ts index 09893b71b..e98a241a7 100644 --- a/mailchimp/loaders/options/lists.ts +++ b/mailchimp/loaders/options/lists.ts @@ -1,6 +1,5 @@ -import { allowCorsFor } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; - +import { allowCorsFor } from "@deco/deco"; export default async function loader( _props: unknown, req: Request, @@ -9,9 +8,7 @@ export default async function loader( Object.entries(allowCorsFor(req)).map(([name, value]) => { ctx.response.headers.set(name, value); }); - const response = await ctx.invoke.mailchimp.loaders.lists({ count: 20 }); - return response.lists.map((list) => ({ label: list.name, value: list.id, diff --git a/mailchimp/mod.ts b/mailchimp/mod.ts index badd17ea4..ff40c7f88 100644 --- a/mailchimp/mod.ts +++ b/mailchimp/mod.ts @@ -1,15 +1,17 @@ -import type { App as A, AppContext as AC, ManifestOf } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { createHttpClient } from "../utils/http.ts"; import { PreviewContainer } from "../utils/preview.tsx"; import { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { API } from "./utils/client.ts"; - +import { + type App as A, + type AppContext as AC, + type ManifestOf, +} from "@deco/deco"; export type App = ReturnType<typeof Mailchimp>; export type AppContext = AC<App>; export type AppManifest = ManifestOf<App>; - export interface Props { apiKey: Secret; /** @@ -22,30 +24,23 @@ export interface Props { */ export default function Mailchimp(props: Props) { const { serverPrefix, apiKey } = props; - const headers = new Headers(); headers.set("Authorization", `Basic ${btoa(`anystring:${apiKey.get()}`)}`); - const api = createHttpClient<API>({ base: `https://${serverPrefix}.api.mailchimp.com`, headers, }); - const state = { api }; - const app: A<Manifest, typeof state> = { state, manifest, }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/nuvemshop/mod.ts b/nuvemshop/mod.ts index 3e95eda2c..6ef7f91bb 100644 --- a/nuvemshop/mod.ts +++ b/nuvemshop/mod.ts @@ -1,55 +1,45 @@ -import type { - App as A, - AppContext as AC, - AppMiddlewareContext as AMC, - ManifestOf, -} from "deco/mod.ts"; import { createHttpClient } from "../utils/http.ts"; import workflow from "../workflows/mod.ts"; import { NuvemShopAPI } from "./utils/client.ts"; import { fetchSafe } from "../utils/fetch.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import type { Secret } from "../website/loaders/secret.ts"; - import { ClientOf } from "../utils/http.ts"; import PreviewNuvemshop from "./preview/index.tsx"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { + type App as A, + type AppContext as AC, + type AppMiddlewareContext as AMC, + type ManifestOf, +} from "@deco/deco"; export type App = ReturnType<typeof Nuvemshop>; export type AppContext = AC<App>; export type AppManifest = ManifestOf<App>; export type AppMiddlewareContext = AMC<App>; - const BASE_URL = "https://api.nuvemshop.com.br/"; - interface State { api: ClientOf<NuvemShopAPI>; publicUrl: string; storeId: string; } - export let state: null | State = null; - /** @title Nuvemshop */ export interface Props { /** * @title Public URL * @description Your Nuvemshop url, example: https://yourstore.lojavirtualnuvem.com.br/ */ - publicUrl: string; - /** * @description STORE ID */ storeId: string; - /** * @title Access Token */ accessToken: Secret; - /** * @description Use Nuvemshop as backend platform * @default nuvemshop @@ -57,53 +47,44 @@ export interface Props { */ platform: "nuvemshop"; } - export const color = 0x272D4B; - /** * @title Nuvemshop * @description Loaders, actions and workflows for adding Nuvemshop Commerce Platform to your website. * @category Ecommmerce * @logo https://raw.githubusercontent.com/deco-cx/apps/main/nuvemshop/logo.png */ -export default function Nuvemshop( - { storeId, accessToken, publicUrl }: Props, -) { +export default function Nuvemshop({ storeId, accessToken, publicUrl }: Props) { const stringAccessToken = typeof accessToken === "string" ? accessToken : accessToken?.get?.() ?? ""; - const headers = new Headers(); headers.set("accept", "application/json"); headers.set("Authentication", `bearer ${stringAccessToken}`); headers.set("content-type", "application/json"); - const api = createHttpClient<NuvemShopAPI>({ base: `${BASE_URL}`, fetcher: fetchSafe, headers: headers, }); - state = { api, publicUrl: publicUrl, storeId: storeId, }; - - const app: A<Manifest, State, [ReturnType<typeof workflow>]> = { + const app: A<Manifest, State, [ + ReturnType<typeof workflow>, + ]> = { state, manifest, dependencies: [workflow({})], }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/nuvemshop/runtime.ts b/nuvemshop/runtime.ts index 41d65a98d..da42a2435 100644 --- a/nuvemshop/runtime.ts +++ b/nuvemshop/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/nuvemshop/utils/types.ts b/nuvemshop/utils/types.ts index 0b40cd637..105fa16c8 100644 --- a/nuvemshop/utils/types.ts +++ b/nuvemshop/utils/types.ts @@ -1,22 +1,18 @@ -import type { FnContext } from "deco/types.ts"; - +import { type FnContext } from "@deco/deco"; export interface Account { /** * @description Header to define our app. For example, https://tiendanube.github.io/api-documentation/intro#authentication. */ userAgent: string; - /** * @description The token generated from admin panel. Read here: https://tiendanube.github.io/api-documentation/authentication. */ accessToken: string; - /** * @description The id of the store in nuvemshop. Check: https://tiendanube.github.io/api-documentation/intro#languages-and-internationalization */ storeId: string; } - // https://tiendanube.github.io/api-documentation/resources/product export interface ProductBaseNuvemShop { id: number; @@ -62,7 +58,6 @@ export interface ProductVariant { created_at: Date; updated_at: Date; } - export interface ProductImage { id: number; product_id: number; @@ -72,7 +67,6 @@ export interface ProductImage { updated_at: Date; alt: string; } - export interface Category { id: number; name: LanguageTypes; @@ -84,7 +78,6 @@ export interface Category { created_at: Date; updated_at: Date; } - export type NuvemShopSort = | "user" | "price-ascending" @@ -94,7 +87,6 @@ export type NuvemShopSort = | "created-at-ascending" | "created-at-descending" | "best-selling"; - export interface ProductSearchParams { q?: string; page?: number; @@ -103,36 +95,29 @@ export interface ProductSearchParams { price_min?: string | null; price_max?: string | null; } - export interface LanguageTypes { pt?: string; en?: string; es?: string; } - export interface PriceInterval { minPrice: string; maxPrice: string; quantity: number; } - export type Context = FnContext<{ configNuvemShop?: Account; }>; - function account(acc: Account) { return acc; } - export default account; - export interface UpdateCartResponse { success: boolean; cart: Cart; items: Item[]; free_shipping: null; } - export interface Cart { id: number; token: string; @@ -216,13 +201,11 @@ export interface Cart { total_short: string; total_long: string; } - export interface PaymentDetails { method: null; credit_card_compunknown: null; installments: number; } - export interface ProductElement { id: number; depth: string; @@ -247,7 +230,6 @@ export interface ProductElement { subtotal_short: string; subtotal_long: string; } - export interface PurpleImage { id: number; product_id: number; @@ -257,7 +239,6 @@ export interface PurpleImage { created_at: string; updated_at: string; } - export interface PromotionalDiscount { id: null; store_id: number; @@ -267,7 +248,6 @@ export interface PromotionalDiscount { contents: unknown[]; promotions_applied: unknown[]; } - export interface Item { id: number; name: string; @@ -283,7 +263,6 @@ export interface Item { featured_image: FeaturedImageClass; google_item_categories: null; } - export interface FeaturedImageClass { id: number; image: string; @@ -292,13 +271,11 @@ export interface FeaturedImageClass { position: number; dimensions: Dimensions; } - export interface Dimensions { width: number; height: number; aspect_ratio: number; } - export interface ItemProduct { id: number; name: string; @@ -342,11 +319,11 @@ export interface ItemProduct { depth: string; available: boolean; } - export interface InstallmentsInfo { - "Nuvem Pago": { [key: string]: InstallmentData }; + "Nuvem Pago": { + [key: string]: InstallmentData; + }; } - export interface InstallmentData { installment_value: number; installment_value_cents: number; @@ -354,24 +331,22 @@ export interface InstallmentData { total_value: number; without_interests: boolean; } - export interface MaxInstallments { installment: number; installment_data: InstallmentData; } - export interface PaymentMethodsConfig { "Nuvem Pago": NuvemPago; } - export interface NuvemPago { name: string; - installments_data: { [key: string]: InstallmentData }; + installments_data: { + [key: string]: InstallmentData; + }; show_full_installments: boolean; max_discount: number; has_general_discount: boolean; } - export interface SelectedOrFirstAvailableVariant { id: number; name: string; diff --git a/openai/mod.ts b/openai/mod.ts index 80ccd275c..8d5f72714 100644 --- a/openai/mod.ts +++ b/openai/mod.ts @@ -1,22 +1,17 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Secret } from "../website/loaders/secret.ts"; import { OpenAI } from "./deps.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface Props { apiKey?: Secret; } - export interface State { openAI: OpenAI; } - /** * @title OpenAI */ -export default function App( - state: Props, -): App<Manifest, State> { +export default function App(state: Props): App<Manifest, State> { const getToken = state?.apiKey?.get; try { const openAI = new OpenAI({ @@ -34,5 +29,4 @@ export default function App( throw new Error(`Failed to initialize OpenAI. Please check the API key.`); } } - export type AppContext = AC<ReturnType<typeof App>>; diff --git a/power-reviews/mod.ts b/power-reviews/mod.ts index cd29af018..7aa6f30b4 100644 --- a/power-reviews/mod.ts +++ b/power-reviews/mod.ts @@ -1,5 +1,3 @@ -import type { App, FnContext } from "deco/mod.ts"; - import { fetchSafe } from "../utils/fetch.ts"; import { createHttpClient } from "../utils/http.ts"; import type { Secret } from "../website/loaders/secret.ts"; @@ -7,9 +5,8 @@ import manifest, { Manifest } from "./manifest.gen.ts"; import { PowerReviews } from "./utils/client.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext<State, Manifest>; - export interface Props { /** * @title App Key @@ -28,12 +25,10 @@ export interface Props { */ merchantGroup?: string; } - interface State extends Props { api: ReturnType<typeof createHttpClient<PowerReviews>>; apiWrite: ReturnType<typeof createHttpClient<PowerReviews>>; } - /** * @title Power Reviews * @description Collect more and better Ratings & Reviews and other UGC. Create UGC displays that convert. Analyze to enhance product experience and positioning. @@ -43,12 +38,12 @@ interface State extends Props { export default function App( { appKey, locale, merchantId, merchantGroup }: Props, ) { - if (!appKey) throw new Error("Missing appKey"); - + if (!appKey) { + throw new Error("Missing appKey"); + } const stringAppKey = typeof appKey === "string" ? appKey : appKey?.get?.() ?? ""; - const api = createHttpClient<PowerReviews>({ base: `https://readservices-b2c.powerreviews.com`, fetcher: fetchSafe, @@ -56,7 +51,6 @@ export default function App( Authorization: stringAppKey, }), }); - const apiWrite = createHttpClient<PowerReviews>({ base: `https://writeservices.powerreviews.com`, fetcher: fetchSafe, @@ -64,7 +58,6 @@ export default function App( Authorization: stringAppKey, }), }); - const state = { appKey: stringAppKey, locale, @@ -73,7 +66,6 @@ export default function App( api, apiWrite, }; - const app: App<Manifest, typeof state> = { state, manifest: { @@ -97,15 +89,12 @@ export default function App( }, }, }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./readme.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/ra-trustvox/mod.ts b/ra-trustvox/mod.ts index e53c3a2c4..c0a42d82b 100644 --- a/ra-trustvox/mod.ts +++ b/ra-trustvox/mod.ts @@ -1,21 +1,18 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { PreviewContainer } from "../utils/preview.tsx"; import manifest, { Manifest } from "./manifest.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface State { /** * @title Store ID * @description Store ID available on the Trustvox dashboard. */ storeId: string; - /** * @title Number of reviews in store carousel. * @description Number of reviews that should appear in the store carousel widget. * @default 7 */ numberOfReviewsInStoreCarousel?: number; - /** * @title Enable the staging environment. * @description When enabling the testing environment, the store id must be replaced with a store id from a Trustvox testing environment store. @@ -23,21 +20,16 @@ export interface State { */ enableStaging?: boolean; } - /** * @title RA Trustvox * @description RA trustvox reviews. * @category Review * @logo https://raw.githubusercontent.com/trustvox/deco-apps/enhancement/trustvox-app/ra-trustvox/ra-trustvox.png */ -export default function RATrustvox( - state: State, -): App<Manifest, State> { +export default function RATrustvox(state: State): App<Manifest, State> { return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof RATrustvox>>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/ra-trustvox/sections/TrustvoxCertificate.tsx b/ra-trustvox/sections/TrustvoxCertificate.tsx index 52b69c940..3f6b24a62 100644 --- a/ra-trustvox/sections/TrustvoxCertificate.tsx +++ b/ra-trustvox/sections/TrustvoxCertificate.tsx @@ -1,22 +1,13 @@ -import { SectionProps } from "deco/blocks/section.ts"; import { AppContext } from "../mod.ts"; - +import { type SectionProps } from "@deco/deco"; export default function TrustvoxCertificate( { enableStaging = false }: SectionProps<typeof loader>, ) { const scriptUrl = enableStaging ? "https://storage.googleapis.com/trustvox-certificate-widget-staging/widget.js" : "https://certificate.trustvox.com.br/widget.js"; - - return ( - <script - defer - type="text/javascript" - src={scriptUrl} - /> - ); + return <script defer type="text/javascript" src={scriptUrl} />; } - export const loader = (_props: unknown, _req: Request, ctx: AppContext) => { return { enableStaging: ctx.enableStaging, diff --git a/ra-trustvox/sections/TrustvoxProductReviews.tsx b/ra-trustvox/sections/TrustvoxProductReviews.tsx index d253ff8c0..1e1344079 100644 --- a/ra-trustvox/sections/TrustvoxProductReviews.tsx +++ b/ra-trustvox/sections/TrustvoxProductReviews.tsx @@ -1,31 +1,27 @@ -import { SectionProps } from "deco/blocks/section.ts"; import { ProductDetailsPage } from "../../commerce/types.ts"; import { scriptAsDataURI } from "../../utils/dataURI.ts"; import { AppContext } from "../mod.ts"; - +import { type SectionProps } from "@deco/deco"; declare global { interface Window { - _trustvox: Array< - [string, string | number | Array<string | undefined> | undefined] - >; + _trustvox: Array<[ + string, + string | number | Array<string | undefined> | undefined, + ]>; } } - export interface Props { page: ProductDetailsPage | null; } - export default function TrustvoxProductReviews( { page, storeId, enableStaging }: Props & SectionProps<typeof loader>, ) { const scriptUrl = enableStaging ? "https://static.trustvox.com.br/trustvox-sincero-staging/sincero.js" : "https://static.trustvox.com.br/sincero/sincero.js"; - const productId = page?.product?.productID; const productName = page?.product?.name; const productImage = page?.product?.image?.[0]?.url; - function setupTrustvoxProductReviews( storeId: string, productId?: string, @@ -38,7 +34,6 @@ export default function TrustvoxProductReviews( globalThis.window._trustvox.push(["_productName", productName]); globalThis.window._trustvox.push(["_productPhotos", [productImage]]); } - return ( <> <script @@ -51,11 +46,7 @@ export default function TrustvoxProductReviews( productImage, )} /> - <script - defer - type="text/javascript" - src={scriptUrl} - /> + <script defer type="text/javascript" src={scriptUrl} /> <div id="trustvox-reviews" @@ -66,7 +57,6 @@ export default function TrustvoxProductReviews( </> ); } - export const loader = (_props: unknown, _req: Request, ctx: AppContext) => { return { storeId: ctx.storeId, diff --git a/ra-trustvox/sections/TrustvoxRateConfig.tsx b/ra-trustvox/sections/TrustvoxRateConfig.tsx index d99cd0d03..770cc8059 100644 --- a/ra-trustvox/sections/TrustvoxRateConfig.tsx +++ b/ra-trustvox/sections/TrustvoxRateConfig.tsx @@ -1,49 +1,33 @@ -import { SectionProps } from "deco/blocks/section.ts"; import { scriptAsDataURI } from "../../utils/dataURI.ts"; import { AppContext } from "../mod.ts"; - +import { type SectionProps } from "@deco/deco"; declare global { interface Window { - _trustvox_shelf_rate: Array< - [string, string | number | Array<string | undefined> | undefined] - >; + _trustvox_shelf_rate: Array<[ + string, + string | number | Array<string | undefined> | undefined, + ]>; } } - export default function TrustvoxRateConfig( { storeId, enableStaging }: SectionProps<typeof loader>, ) { const scriptUrl = enableStaging ? "https://storage.googleapis.com/trustvox-rate-staging/widget.js" : "https://rate.trustvox.com.br/widget.js"; - - function setupTrustvoxRateConfig( - storeId: string, - ) { + function setupTrustvoxRateConfig(storeId: string) { globalThis.window._trustvox_shelf_rate = []; globalThis.window._trustvox_shelf_rate.push(["_storeId", storeId]); globalThis.window._trustvox_shelf_rate.push(["_productContainer", "body"]); globalThis.window._trustvox_shelf_rate.push(["_watchSubtree", "true"]); } - return ( <> - <script - defer - src={scriptAsDataURI( - setupTrustvoxRateConfig, - storeId, - )} - /> - <script - defer - type="text/javascript" - src={scriptUrl} - /> + <script defer src={scriptAsDataURI(setupTrustvoxRateConfig, storeId)} /> + <script defer type="text/javascript" src={scriptUrl} /> </> ); } - export const loader = (_props: unknown, _req: Request, ctx: AppContext) => { return { storeId: ctx.storeId, diff --git a/ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx b/ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx index e59a62073..aeac6340b 100644 --- a/ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx +++ b/ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx @@ -1,15 +1,14 @@ -import { SectionProps } from "deco/blocks/section.ts"; import { scriptAsDataURI } from "../../utils/dataURI.ts"; import { AppContext } from "../mod.ts"; - +import { type SectionProps } from "@deco/deco"; declare global { interface Window { - _trustvox_colt: Array< - [string, string | number | Array<string | undefined> | undefined] - >; + _trustvox_colt: Array<[ + string, + string | number | Array<string | undefined> | undefined, + ]>; } } - export default function TrustvoxStoreReviewsCarousel( { storeId, enableStaging, numberOfReviewsInStoreCarousel }: SectionProps< typeof loader @@ -18,7 +17,6 @@ export default function TrustvoxStoreReviewsCarousel( const scriptUrl = enableStaging ? "https://storage.googleapis.com/trustvox-colt-staging/colt.min.js" : "https://colt.trustvox.com.br/colt.min.js"; - function setupTrustvoxStoreReviewsCarousel( storeId: string, numberOfReviewsInStoreCarousel?: number, @@ -30,7 +28,6 @@ export default function TrustvoxStoreReviewsCarousel( numberOfReviewsInStoreCarousel, ]); } - return ( <> <script @@ -41,17 +38,12 @@ export default function TrustvoxStoreReviewsCarousel( numberOfReviewsInStoreCarousel, )} /> - <script - defer - type="text/javascript" - src={scriptUrl} - /> + <script defer type="text/javascript" src={scriptUrl} /> <div id="_trustvox_colt" class="my-8" /> </> ); } - export const loader = (_props: unknown, _req: Request, ctx: AppContext) => { return { storeId: ctx.storeId, diff --git a/records/deps.ts b/records/deps.ts index b614c79b1..461cf0294 100644 --- a/records/deps.ts +++ b/records/deps.ts @@ -1,6 +1,5 @@ -import { context } from "deco/deco.ts"; +import { context } from "@deco/deco"; export * from "https://esm.sh/drizzle-orm@0.30.10/libsql"; export * from "npm:@libsql/client@0.7.0"; - export const createLocalClient = !context.isDeploy && (await import("npm:@libsql/client@0.7.0/node")).createClient; diff --git a/records/mod.ts b/records/mod.ts index aeca5addb..b73fec5d2 100644 --- a/records/mod.ts +++ b/records/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { createClient as createSQLClient, @@ -7,41 +6,34 @@ import { } from "./deps.ts"; import { getSQLClientConfig, StorageConfig } from "./utils.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - -export interface Props extends StorageConfig {} - +import { type App, type AppContext as AC } from "@deco/deco"; +export interface Props extends StorageConfig { +} /** * @title Deco Records * @description Deco database for records * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/records/logo.png */ -export default function Records( - { url, authToken, ...state }: Props, -) { +export default function Records({ url, authToken, ...state }: Props) { const sqlClientConfig = getSQLClientConfig({ url, authToken }); const sqlClient = sqlClientConfig.url.startsWith("file://") && createLocalClient ? createLocalClient(sqlClientConfig) : createSQLClient(sqlClientConfig); - const appState = { ...state, sqlClient, drizzle: drizzle(sqlClient), }; - const app: App<Manifest, typeof appState> = { manifest, state: appState, }; - return app; } - export type RecordsApp = ReturnType<typeof Records>; export type AppContext = AC<RecordsApp>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/records/utils.ts b/records/utils.ts index b9dbfa158..e709936fa 100644 --- a/records/utils.ts +++ b/records/utils.ts @@ -1,8 +1,7 @@ -import { context } from "deco/live.ts"; import { Secret } from "../website/loaders/secret.ts"; import { brightGreen, brightRed } from "std/fmt/colors.ts"; import { join } from "https://deno.land/std@0.204.0/path/join.ts"; - +import { context } from "@deco/deco"; export interface StorageConfig { /** * @title Url @@ -14,19 +13,15 @@ export interface StorageConfig { */ authToken: Secret; } - export const getLocalDbFilename = () => join(Deno.cwd(), "sqlite.db"); - export const getLocalSQLClientConfig = () => ({ url: new URL(`file://${getLocalDbFilename()}`).href, authToken: "", }); - export const getSQLClientConfig = ({ authToken, url }: StorageConfig) => { const useProdDb = Deno.env.get("USE_PRODUCTION_DB"); const useLocalDB = useProdDb !== undefined && useProdDb !== "1" || useProdDb === undefined && !context.isDeploy; - if (useLocalDB) { console.log( `You're using ${ @@ -37,7 +32,6 @@ export const getSQLClientConfig = ({ authToken, url }: StorageConfig) => { ); return getLocalSQLClientConfig(); } - console.log( `You're using ${ brightRed("production database") @@ -45,7 +39,6 @@ export const getSQLClientConfig = ({ authToken, url }: StorageConfig) => { brightRed("USE_PRODUCTION_DB") }' environment variable.\n`, ); - return { url, authToken: authToken?.get?.() ?? "", diff --git a/resend/README.md b/resend/README.md index f34bf884f..df2c07a47 100644 --- a/resend/README.md +++ b/resend/README.md @@ -14,7 +14,7 @@ Resend is an email platform that helps developers build and send transactional a ```typescript // runtime.ts - import { proxy } from "deco/clients/withManifest.ts"; + import { proxy } from "@deco/deco/web"; import type { Manifest } from "./manifest.gen.ts"; import type { Manifest as ManifestVNDA } from "apps/vnda/manifest.gen.ts"; import type { Manifest as ManifestVTEX } from "apps/vtex/manifest.gen.ts"; diff --git a/resend/mod.ts b/resend/mod.ts index fb03f1c50..c49f05e81 100644 --- a/resend/mod.ts +++ b/resend/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { fetchSafe } from "../utils/fetch.ts"; import { createHttpClient } from "../utils/http.ts"; @@ -6,12 +5,11 @@ import { PreviewContainer } from "../utils/preview.tsx"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { ResendApi } from "./utils/client.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface EmailFrom { name?: string; domain?: string; } - export interface Props { /**@title API KEY Resend */ apiKey?: Secret; @@ -29,31 +27,26 @@ export interface Props { */ subject?: string; } - export interface State extends Props { apiWrite: ReturnType<typeof createHttpClient<ResendApi>>; } - /** * @title Resend * @description app for sending emails using https://resend.com/ * @logo https://raw.githubusercontent.com/deco-cx/apps/main/resend/logo.png */ -export default function App( - { - apiKey, - emailFrom = { - name: "Contact", - domain: "<onboarding@resend.dev>", - }, - emailTo, - subject = "Contato via app resend", - }: State, -): App<Manifest, State> { +export default function App({ + apiKey, + emailFrom = { + name: "Contact", + domain: "<onboarding@resend.dev>", + }, + emailTo, + subject = "Contato via app resend", +}: State): App<Manifest, State> { const apiKeyToken = typeof apiKey === "string" ? apiKey : apiKey?.get?.() ?? ""; - const apiWrite = createHttpClient<ResendApi>({ base: "https://api.resend.com/emails", fetcher: fetchSafe, @@ -62,7 +55,6 @@ export default function App( "Content-Type": "application/json", }), }); - const state = { apiKey, emailFrom, @@ -70,21 +62,17 @@ export default function App( subject, apiWrite, }; - const app: App<Manifest, typeof state> = { manifest, state, }; return app; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/scripts/new.ts b/scripts/new.ts index 43cd8c865..b4db6f011 100644 --- a/scripts/new.ts +++ b/scripts/new.ts @@ -13,7 +13,7 @@ await Deno.writeTextFile( await Deno.writeTextFile( join(Deno.cwd(), appName, "mod.ts"), ` -import type { App, AppContext as AC } from "deco/mod.ts"; +import type { App, AppContext as AC } from "@deco/deco"; import manifest, { Manifest } from "./manifest.gen.ts"; export interface State { diff --git a/shopify/mod.ts b/shopify/mod.ts index add53caf9..7d4546de5 100644 --- a/shopify/mod.ts +++ b/shopify/mod.ts @@ -1,4 +1,3 @@ -import type { App, FnContext } from "deco/mod.ts"; import { fetchSafe } from "../utils/fetch.ts"; import { createGraphqlClient } from "../utils/graphql.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; @@ -6,31 +5,26 @@ import getStateFromZip from "../commerce/utils/stateByZip.ts"; import type { Secret } from "../website/loaders/secret.ts"; import { PreviewContainer } from "../utils/preview.tsx"; import { Markdown } from "../decohub/components/Markdown.tsx"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext<State, Manifest>; - /** @title Shopify */ export interface Props { /** * @description Shopify store name. */ storeName: string; - /** * @title Access Token * @description Shopify storefront access token. */ storefrontAccessToken: string; - /** * @ttile Access Token * @description Shopify admin access token. */ adminAccessToken: Secret; - /** @description Disable password protection on the store */ storefrontDigestCookie?: string; - /** * @description Use Shopify as backend platform * @default shopify @@ -38,23 +32,18 @@ export interface Props { */ platform: "shopify"; } - export interface Address { provinceCode: string; } - export interface AddressLocator { byZipCode(zip: string): Promise<Address | null>; } - export interface State extends Props { storefront: ReturnType<typeof createGraphqlClient>; admin: ReturnType<typeof createGraphqlClient>; address: AddressLocator; } - export const color = 0x96BF48; - /** * @title Shopify * @description Loaders, actions and workflows for adding Shopify Commerce Platform to your website. @@ -63,11 +52,9 @@ export const color = 0x96BF48; */ export default function App(props: Props): App<Manifest, State> { const { storeName, storefrontAccessToken, adminAccessToken } = props; - const stringAdminAccessToken = typeof adminAccessToken === "string" ? adminAccessToken : adminAccessToken?.get?.() ?? ""; - const storefront = createGraphqlClient({ fetcher: fetchSafe, endpoint: `https://${storeName}.myshopify.com/api/2023-07/graphql.json`, @@ -85,24 +72,20 @@ export default function App(props: Props): App<Manifest, State> { "X-Shopify-Access-Token": stringAdminAccessToken, }), }); - const byZipCode = (zip: string) => { return Promise.resolve({ provinceCode: getStateFromZip(zip), }); }; - return { state: { ...props, admin, storefront, address: { byZipCode } }, manifest, }; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/shopify/runtime.ts b/shopify/runtime.ts index 41d65a98d..da42a2435 100644 --- a/shopify/runtime.ts +++ b/shopify/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/smarthint/loaders/productListingPage.ts b/smarthint/loaders/productListingPage.ts index a21f3d627..7ee87e504 100644 --- a/smarthint/loaders/productListingPage.ts +++ b/smarthint/loaders/productListingPage.ts @@ -9,12 +9,11 @@ import { toProduct, toSortOption, } from "../utils/transform.ts"; -import { redirect } from "deco/mod.ts"; import { getSessionCookie } from "../utils/getSession.ts"; import { FilterProp, SearchSort, SHProduct } from "../utils/typings.ts"; import { RuleType } from "./PLPBanners.ts"; import { getCategoriesParam } from "./recommendations.ts"; - +import { redirect } from "@deco/deco"; export interface Props { /** * @hide @@ -54,7 +53,6 @@ export interface Props { validation?: string; }; } - /** * @title SmartHint Integration - Product List Page * @description Product List Page @@ -75,31 +73,21 @@ const loader = async ( rule, ruletype, } = props; - const url = new URL(req.url); - const { page, from } = resolvePage(url, size, fromParam); - const sort = getSortParam(url, searchSort); - const filters = getFilterParam(url, filter) ?? []; - const { anonymous } = getSessionCookie(req.headers); - const categories = categoryTree ? getCategoriesParam({ categoryTree, url }) : undefined; - const categoriesFilter = categories ? [`categories:${categories}`] : []; - const term = termProp ?? url.searchParams.get("busca") ?? url.searchParams.get("q") ?? undefined; - const conditionString = condition?.field && condition.value && condition.validation ? `valueDouble:${condition.field}:${condition.value}:validation:${condition.validation}` : undefined; - const commonParams = { cluster, shcode, @@ -112,7 +100,6 @@ const loader = async ( filter: [...filters, ...categoriesFilter], condition: conditionString, }; - const data = term || categories ? await api["GET /:cluster/Search/GetPrimarySearch"]({ ...commonParams, @@ -122,23 +109,19 @@ const loader = async ( ...commonParams, url: url.pathname.replace("/", ""), }).then((r) => r.json()).then((result) => result.SearchResult); - - if (!data) return null; - + if (!data) { + return null; + } if (data.IsRedirect) { redirect( new URL(data?.urlRedirect!, url.origin) .href, ); } - const products = data?.Products?.map((product) => toProduct(product as SHProduct)) ?? []; - const sortOptions = toSortOption(data?.Sorts ?? []); - const resultFilters = toFilters(data?.Filters ?? [], url); - const { nextPage, previousPage } = getPaginationInfo( url, size, @@ -146,7 +129,6 @@ const loader = async ( page, data?.TotalResult, ); - return { "@type": "ProductListingPage", products: products, @@ -169,5 +151,4 @@ const loader = async ( }, }; }; - export default loader; diff --git a/smarthint/mod.ts b/smarthint/mod.ts index 276590e58..81868027a 100644 --- a/smarthint/mod.ts +++ b/smarthint/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import { Category } from "../commerce/types.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { fetchSafe } from "../utils/fetch.ts"; @@ -6,7 +5,7 @@ import { createHttpClient } from "../utils/http.ts"; import { PreviewContainer } from "../utils/preview.tsx"; import manifest from "./manifest.gen.ts"; import { OpenAPI } from "./utils/openapi/smarthint.openapi.gen.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export interface State { /** * @description Your SmartHint Code. Get this information in your Admin Panel. (SH-XXXXX) @@ -26,21 +25,17 @@ export interface State { */ categoryTree?: Category | Category[]; } - /** * @title SmartHint * @description Smart search and product recommendation to improve your eCommerce customer experience. * @category Search * @logo https://raw.githubusercontent.com/deco-cx/apps/main/smarthint/logo.png */ -export default function App( - props: State, -) { +export default function App(props: State) { const headers = new Headers(); headers.set("accept", "application/json"); headers.set("content-type", "application/json"); headers.set("Cache-Control", "no-cache"); - const api = createHttpClient<OpenAPI>({ base: "https://searches.smarthint.co/", fetcher: fetchSafe, @@ -51,30 +46,24 @@ export default function App( fetcher: fetchSafe, headers: headers, }); - const publicUrl = (new URL( props.publicUrl?.startsWith("http") ? props.publicUrl : `https://${props.publicUrl}`, )).origin; - const state = { ...props, publicUrl, api, recs, }; - return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { @@ -94,5 +83,4 @@ export const preview = async () => { }, }; }; - // TODO fix image path diff --git a/smarthint/runtime.ts b/smarthint/runtime.ts index 41d65a98d..da42a2435 100644 --- a/smarthint/runtime.ts +++ b/smarthint/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/smarthint/sections/Analytics/SmarthintTracking.tsx b/smarthint/sections/Analytics/SmarthintTracking.tsx index 8a6dc5941..b1abb65e3 100644 --- a/smarthint/sections/Analytics/SmarthintTracking.tsx +++ b/smarthint/sections/Analytics/SmarthintTracking.tsx @@ -2,9 +2,8 @@ import { AppContext } from "../../mod.ts"; import { Props as ClickProps } from "../../actions/click.ts"; import { PageType } from "../../utils/typings.ts"; import { ANONYMOUS_COOKIE, SESSION_COOKIE } from "../../utils/getSession.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import { sortPagesPattern } from "../../utils/sortPagesPattern.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; declare global { interface Window { smarthint: { @@ -12,36 +11,31 @@ declare global { }; } } - -const listener = ({ - shcode, - pagesPathPattern, - publicUrl, - SESSION_COOKIE, - ANONYMOUS_COOKIE, -}: ReturnType<typeof loader>) => { +const listener = ( + { shcode, pagesPathPattern, publicUrl, SESSION_COOKIE, ANONYMOUS_COOKIE }: + ReturnType<typeof loader>, +) => { const prodURL = new URL(publicUrl); - const url = new URL(window.location.href); url.host = prodURL.host; url.port = ""; - const pageType = pagesPathPattern.find(({ page }) => new URLPattern({ pathname: page }).test(url.href) )?.pageType ?? "others"; - - const click = ({ - productGroupID, - position, - clickFeature, - term, - positionRecommendation, - productPrice, - shippingPrice, - shippingTime, - clickProduct, - }: ClickProps) => { + const click = ( + { + productGroupID, + position, + clickFeature, + term, + positionRecommendation, + productPrice, + shippingPrice, + shippingTime, + clickProduct, + }: ClickProps, + ) => { const clickUrl = new URL("https://recs.smarthint.co/track/click"); const date = new Date().toLocaleString().replace(",", ""); const pageUrl = new URL(url); @@ -50,7 +44,6 @@ const listener = ({ pageUrl.searchParams.get("q"); const session = getCookie(SESSION_COOKIE) ?? ""; const anonymousConsumer = getCookie(ANONYMOUS_COOKIE) ?? ""; - clickUrl.searchParams.set("clickFeature", clickFeature); clickUrl.searchParams.set("shcode", shcode); clickUrl.searchParams.set("clickProduct", clickProduct); @@ -63,8 +56,9 @@ const listener = ({ clickUrl.searchParams.set("productPrice", String(productPrice)); clickUrl.searchParams.set("anonymousConsumer", anonymousConsumer); clickUrl.searchParams.set("session", session); - - if (term || pathTerm) clickUrl.searchParams.set("term", term ?? pathTerm); + if (term || pathTerm) { + clickUrl.searchParams.set("term", term ?? pathTerm); + } if (shippingPrice) { clickUrl.searchParams.set("shippingPrice", String(shippingPrice)); } @@ -74,27 +68,21 @@ const listener = ({ if (shippingTime) { clickUrl.searchParams.set("shippingTime", String(shippingTime)); } - fetch(clickUrl); }; - const setup = () => { globalThis.window.smarthint = { click }; }; - function getCookie(name: string): string | null { const cookies = document.cookie.split(";"); - for (let cookie of cookies) { cookie = cookie.trim(); if (cookie.startsWith(name + "=")) { return cookie.substring(name.length + 1); } } - return null; } - function setCookie(name: string, value: string, seconds: number): void { let expires = ""; if (seconds) { @@ -104,7 +92,6 @@ const listener = ({ } document.cookie = `${name}=${value || ""}${expires}; path=/`; } - function resetCookieExpiration( name: string, value: string, @@ -121,12 +108,9 @@ const listener = ({ 999, ); const dateLimit = dateExpiration.getTime() - date.getTime(); - const cookieLimit = dateLimit < seconds ? dateLimit : seconds; - setCookie(name, value, cookieLimit); } - function setupCookieResetOnInteraction( cookieName: string, cookieValue: string, @@ -135,14 +119,12 @@ const listener = ({ const resetCookie = () => { resetCookieExpiration(cookieName, cookieValue, expirationSeconds); }; - document.addEventListener("click", resetCookie); document.addEventListener("keydown", resetCookie); document.addEventListener("mousemove", resetCookie); document.addEventListener("scroll", resetCookie); document.addEventListener("touchstart", resetCookie); } - function createUserToken() { if ( typeof crypto !== "undefined" && @@ -150,37 +132,28 @@ const listener = ({ ) { return crypto.randomUUID(); } - return (Math.random() * 1e9).toFixed(); } - function setupSession() { const sessionValue = getCookie(SESSION_COOKIE) ?? createUserToken(); - setupCookieResetOnInteraction(SESSION_COOKIE, sessionValue, 30 * 60 * 1000); } - function setupAnonymous() { const anonymousValue = getCookie(ANONYMOUS_COOKIE) ?? createUserToken(); - setupCookieResetOnInteraction( ANONYMOUS_COOKIE, anonymousValue, 30 * 60 * 1000, ); } - const pageView = () => { globalThis.window.addEventListener("load", () => { const pageUrl = new URL(url); const origin = pageUrl.origin; const date = new Date().toLocaleString().replace(",", ""); - const pageViewUrl = new URL("https://recs.smarthint.co/track/pageView"); - const session = getCookie(SESSION_COOKIE) ?? ""; const anonymousConsumer = getCookie(ANONYMOUS_COOKIE) ?? ""; - pageViewUrl.searchParams.set("shcode", shcode); pageViewUrl.searchParams.set("url", pageUrl.href); pageViewUrl.searchParams.set("origin", origin); @@ -189,24 +162,20 @@ const listener = ({ pageViewUrl.searchParams.set("date", date); pageViewUrl.searchParams.set("session", session); pageViewUrl.searchParams.set("anonymousConsumer", anonymousConsumer); - fetch(pageViewUrl); }); }; - setup(); setupSession(); setupAnonymous(); pageView(); }; - /** * @title SmartHint Tracking */ function Analytics(props: ReturnType<typeof loader>) { return <script defer src={useScriptAsDataURI(listener, props)} />; } - /** * @title {{{page}}} */ @@ -218,16 +187,12 @@ export interface Page { page: string; pageType: PageType; } - export interface Props { pages: Page[]; } - export const loader = (props: Props, _req: Request, ctx: AppContext) => { const { shcode, publicUrl } = ctx; - const pagesSorted = sortPagesPattern(props.pages); - return { shcode, publicUrl, @@ -236,5 +201,4 @@ export const loader = (props: Props, _req: Request, ctx: AppContext) => { ANONYMOUS_COOKIE, }; }; - export default Analytics; diff --git a/sourei/mod.ts b/sourei/mod.ts index 87f956b6c..f9913730a 100644 --- a/sourei/mod.ts +++ b/sourei/mod.ts @@ -1,7 +1,6 @@ -import type { App } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App } from "@deco/deco"; /** * @title Sourei * @description Get sourei to analyze your data. @@ -10,10 +9,8 @@ import { PreviewContainer } from "../utils/preview.tsx"; */ export default function App() { const app: App<Manifest> = { manifest, state: {} }; - return app; } - export const preview = () => { return { Component: PreviewContainer, diff --git a/sourei/sections/Analytics/Sourei.tsx b/sourei/sections/Analytics/Sourei.tsx index c0a37e17b..c109922e0 100644 --- a/sourei/sections/Analytics/Sourei.tsx +++ b/sourei/sections/Analytics/Sourei.tsx @@ -1,7 +1,6 @@ // deno-lint-ignore-file no-explicit-any import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; interface Props { /** * @title Hostname @@ -14,68 +13,55 @@ interface Props { * @description e.g: GTM-XXXXXXXX */ gtmId: string; - /** * @description Adds the defer attribute to script tag. Set eager for including the script asap * @default defer */ loading?: "module" | "defer" | "async" | "eager"; } - const snippet = () => { globalThis.window.dataLayer = globalThis.window.dataLayer || []; globalThis.window.dataLayer.push({ "gtm.start": new Date().getTime(), event: "gtm.js", }); - const rounded = (n: number) => Number(n.toFixed(2)); - const fixId = ({ item_id, item_group_id, _item_url, ...rest }: any) => item_group_id ? { item_id: `${item_group_id}_${item_id}`, ...rest } : { item_id, ...rest }; - const fixPrices = ({ price, discount = 0, quantity = 1, ...rest }: any) => ({ ...rest, quantity, discount: rounded(discount), price: rounded(price + discount), }); - const fixIndex = ({ index, ...rest }: any) => ({ ...rest, index: index + 1 }); - globalThis.window.DECO.events.subscribe((event) => { - if (!event) return; - + if (!event) { + return; + } if (event.name === "deco") { globalThis.window.dataLayer.push(event); return; } - const ecommerce: any = { ...event.params }; - if (ecommerce && Array.isArray(ecommerce.items)) { ecommerce.items = ecommerce.items.map(fixId).map(fixPrices).map(fixIndex); } - if (typeof ecommerce.value === "number") { ecommerce.value = rounded(ecommerce.value); } - globalThis.window.dataLayer.push({ ecommerce: null }); globalThis.window.dataLayer.push({ event: event.name, ecommerce }); }); }; - -function Section({ - gtmId, - hostname = "https://www.googletagmanager.com", - loading = "defer", -}: Props) { +function Section( + { gtmId, hostname = "https://www.googletagmanager.com", loading = "defer" }: + Props, +) { const src = new URL(`/gtm.js?id=${gtmId}`, hostname); const iframeSrc = new URL(`/ns.html?id=${gtmId}`, hostname); - return ( <> {/* Head */} @@ -105,5 +91,4 @@ function Section({ </> ); } - export default Section; diff --git a/typesense/mod.ts b/typesense/mod.ts index b8b6b043e..583f420fe 100644 --- a/typesense/mod.ts +++ b/typesense/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import Typesense from "npm:typesense@1.7.1"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewContainer } from "../utils/preview.tsx"; @@ -8,48 +7,38 @@ import { ProductsCollectionName, setupProductsCollection, } from "./utils/product.ts"; - +import { type App, type AppContext as AC } from "@deco/deco"; export type AppContext = AC<ReturnType<typeof App>>; - export interface State { /** * @title Your TypeSense location * @description e.g.: https://dry-pond-5415.fly.dev */ apiUrls: string[]; - /** * @title API Key * @description https://dashboard.algolia.com/account/api-keys/all */ apiKey: Secret; } - export type Collections = ProductsCollectionName; - /** * @title TypeSense * @description Open source search engine meticulously engineered for performance & ease-of-use. * @category Search * @logo https://raw.githubusercontent.com/deco-cx/apps/main/typesense/logo.png */ -export default function App( - props: State, -) { +export default function App(props: State) { const { apiKey, apiUrls } = props; - if (!apiKey) { throw new Error("Missing API key"); } - const stringApiKey = typeof apiKey === "string" ? apiKey : apiKey?.get?.() ?? ""; - const options = { nodes: apiUrls.map((href) => { const url = new URL(href); - return { protocol: url.protocol.replace(":", ""), host: url.host, @@ -64,7 +53,6 @@ export default function App( healthcheckIntervalSeconds: 5, logLevel: "error" as const, }; - const client = new Typesense.Client({ ...options, numRetries: 10, @@ -77,22 +65,17 @@ export default function App( connectionTimeoutSeconds: 5, retryIntervalSeconds: 0.1, }); - const state = { ...props, products: setupProductsCollection(client, searchClient), }; - const app: App<Manifest, typeof state> = { manifest, state }; - return app; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/typesense/workflows/index/product.ts b/typesense/workflows/index/product.ts index ba668afb2..a80cc50db 100644 --- a/typesense/workflows/index/product.ts +++ b/typesense/workflows/index/product.ts @@ -1,7 +1,7 @@ -import { WorkflowContext, WorkflowGen } from "deco/mod.ts"; import { Product } from "../../../commerce/types.ts"; import type { Manifest } from "../../manifest.gen.ts"; - +import { WorkflowContext } from "@deco/deco/blocks"; +import { type WorkflowGen } from "@deco/deco"; /** * @title Typesense integration - Product Event */ @@ -15,24 +15,21 @@ export default function Index(_: unknown) { const productID = product?.productID; const name = product?.name; const groupName = product?.isVariantOf?.name; - if (type !== "Product" || !productID) { throw new Error(`Workflow input expected Product but received ${type}`); } - - yield ctx.log( - "[Typesense] Started indexing Product:", - { productID, name, groupName, action }, - ); - - yield ctx.invoke( - "typesense/actions/index/product.ts", - { product, action }, - ); - - yield ctx.log( - "[Typesense] Finished indexing Product:", - { productID, name, groupName, action }, - ); + yield ctx.log("[Typesense] Started indexing Product:", { + productID, + name, + groupName, + action, + }); + yield ctx.invoke("typesense/actions/index/product.ts", { product, action }); + yield ctx.log("[Typesense] Finished indexing Product:", { + productID, + name, + groupName, + action, + }); }; } diff --git a/utils/cookie.ts b/utils/cookie.ts index 94b2561e9..6a37c72d9 100644 --- a/utils/cookie.ts +++ b/utils/cookie.ts @@ -1,13 +1,10 @@ -import { DECO_SEGMENT } from "deco/mod.ts"; -import { Flag } from "deco/types.ts"; -import { tryOrDefault } from "deco/utils/object.ts"; import { getCookies, getSetCookies, setCookie } from "std/http/cookie.ts"; - +import { DECO_SEGMENT, type Flag } from "@deco/deco"; +import { tryOrDefault } from "@deco/deco/utils"; export const getFlagsFromRequest = (req: Request) => { const cookies = getCookies(req.headers); return getFlagsFromCookies(cookies); }; - export const getFlagsFromCookies = (cookies: Record<string, string>) => { const flags: Flag[] = []; const segment = cookies[DECO_SEGMENT] @@ -16,24 +13,20 @@ export const getFlagsFromCookies = (cookies: Record<string, string>) => { {}, ) : {}; - segment.active?.forEach((flag: string) => flags.push({ name: flag, value: true }) ); segment.inactiveDrawn?.forEach((flag: string) => flags.push({ name: flag, value: false }) ); - return flags; }; - export const proxySetCookie = ( from: Headers, to: Headers, toDomain?: URL | string, ) => { const newDomain = toDomain && new URL(toDomain); - for (const cookie of getSetCookies(from)) { const newCookie = newDomain ? { @@ -41,7 +34,6 @@ export const proxySetCookie = ( domain: newDomain.hostname, } : cookie; - setCookie(to, newCookie); } }; diff --git a/utils/fetch.ts b/utils/fetch.ts index eb6aba199..bacc59122 100644 --- a/utils/fetch.ts +++ b/utils/fetch.ts @@ -1,74 +1,63 @@ import "../website/utils/unhandledRejection.ts"; - import { ExponentialBackoff, handleWhen, retry, } from "https://esm.sh/cockatiel@3.1.1?target=es2019"; import { HttpError } from "./http.ts"; -import { fetch } from "deco/runtime/fetch/mod.ts"; - +import { fetch } from "@deco/deco"; // this error is thrown by deno deploy when the connection is closed by the server. // check the discussion at discord: https://discord.com/channels/985687648595243068/1107104244517048320/1107111259813466192 export const CONNECTION_CLOSED_MESSAGE = "connection closed before message completed"; - const connectionClosedMsg = handleWhen((err: Error | null) => { const isConnectionClosed = err?.message?.includes(CONNECTION_CLOSED_MESSAGE) ?? false; - - if (isConnectionClosed) console.error("retrying...", err); - + if (isConnectionClosed) { + console.error("retrying...", err); + } return isConnectionClosed; }); - export const retryExceptionOr500 = retry(connectionClosedMsg, { maxAttempts: 1, backoff: new ExponentialBackoff(), }); - type CachingMode = "stale-while-revalidate"; - type DecoInit = { cache: CachingMode; - cacheTtlByStatus?: Array<{ from: number; to: number; ttl: number }>; + cacheTtlByStatus?: Array<{ + from: number; + to: number; + ttl: number; + }>; +}; +export type DecoRequestInit = RequestInit & { + deco?: DecoInit; }; - -export type DecoRequestInit = RequestInit & { deco?: DecoInit }; - export const fetchSafe = async ( input: string | Request | URL, init?: DecoRequestInit, ) => { const response = await retryExceptionOr500.execute(() => fetch(input, init)); - if (response.ok) { return response; } - const isManual = init?.redirect === "manual"; const isRedirect = response.status === 301 || response.status === 302; - if (isManual && isRedirect) { return response; } - throw new HttpError(response.status, `${await response.text()}`); }; - export const fetchAPI = async <T>( input: string | Request | URL, init?: DecoRequestInit, ): Promise<T> => { const headers = new Headers(init?.headers); - headers.set("accept", "application/json"); - const response = await fetchSafe(input, { ...init, headers }); - return response.json(); }; - export const STALE = { deco: { cache: "stale-while-revalidate" }, } as const; diff --git a/utils/framework.tsx b/utils/framework.tsx index 7eb1876bb..2b66626a8 100644 --- a/utils/framework.tsx +++ b/utils/framework.tsx @@ -1,6 +1,5 @@ -import { PreactComponent } from "deco/engine/block.ts"; -import { useFramework } from "deco/runtime/handler.tsx"; import { green } from "std/fmt/colors.ts"; +import { PreactComponent, useFramework } from "@deco/deco"; export const errorIfFrameworkMismatch = ( flavor: string, page: PreactComponent, diff --git a/utils/http.ts b/utils/http.ts index bef629dc8..a6a86ebc9 100644 --- a/utils/http.ts +++ b/utils/http.ts @@ -1,6 +1,5 @@ -import { RequestInit } from "deco/runtime/fetch/mod.ts"; +import { type RequestInit } from "@deco/deco"; import { fetchSafe } from "./fetch.ts"; - const HTTP_VERBS = new Set( [ "GET", @@ -11,41 +10,47 @@ const HTTP_VERBS = new Set( "HEAD", ] as const, ); - export class HttpError extends Error { constructor(public status: number, message?: string, options?: ErrorOptions) { super(message, options); this.name = `HttpError ${status}`; } } - export interface TypedRequestInit<T> extends Omit<RequestInit, "body"> { body: T; } - export interface TypedResponse<T> extends Response { json: () => Promise<T>; } - type HttpVerb = typeof HTTP_VERBS extends Set<infer Verb> ? Verb : never; - type URLPatternParam = string | number; - type URLPatternParams<URL extends string> = URL extends - `/:${infer param}/${infer rest}` - ? { [key in param]: URLPatternParam } & URLPatternParams<`/${rest}`> - : URL extends `/:${infer param}?` ? { [key in param]?: URLPatternParam } - : URL extends `/:${infer param}` ? { [key in param]: URLPatternParam } - : URL extends `/*?` ? { "*"?: URLPatternParam | URLPatternParam[] } - : URL extends `/*` ? { "*": URLPatternParam | URLPatternParam[] } - : URL extends `/*${infer param}?` - ? { [key in param]: URLPatternParam | URLPatternParam[] } - : URL extends `/*${infer param}` - ? { [key in param]: URLPatternParam | URLPatternParam[] } + `/:${infer param}/${infer rest}` ? + & { + [key in param]: URLPatternParam; + } + & URLPatternParams<`/${rest}`> + : URL extends `/:${infer param}?` ? { + [key in param]?: URLPatternParam; + } + : URL extends `/:${infer param}` ? { + [key in param]: URLPatternParam; + } + : URL extends `/*?` ? { + "*"?: URLPatternParam | URLPatternParam[]; + } + : URL extends `/*` ? { + "*": URLPatternParam | URLPatternParam[]; + } + : URL extends `/*${infer param}?` ? { + [key in param]: URLPatternParam | URLPatternParam[]; + } + : URL extends `/*${infer param}` ? { + [key in param]: URLPatternParam | URLPatternParam[]; + } : URL extends `/${string}/${infer rest}` ? URLPatternParams<`/${rest}`> // deno-lint-ignore ban-types : {}; - export type ClientOf<T> = { [key in (keyof T) & `${HttpVerb} /${string}`]: key extends `${HttpVerb} /${infer path}` ? T[key] extends { @@ -66,78 +71,66 @@ export type ClientOf<T> = { : never : never; }; - export interface HttpClientOptions { base: string; headers?: Headers; processHeaders?: (headers?: Headers) => Headers | undefined; fetcher?: typeof fetch; } - -export const createHttpClient = <T>({ - base: maybeBase, - headers: defaultHeaders, - processHeaders = (headers?: Headers) => headers, - fetcher = fetchSafe, -}: HttpClientOptions): ClientOf<T> => { +export const createHttpClient = <T>( + { + base: maybeBase, + headers: defaultHeaders, + processHeaders = (headers?: Headers) => headers, + fetcher = fetchSafe, + }: HttpClientOptions, +): ClientOf<T> => { // Base should always endwith / so when concatenating with path we get the right URL const base = maybeBase.at(-1) === "/" ? maybeBase : `${maybeBase}/`; - return new Proxy({} as ClientOf<T>, { get: (_target, prop) => { if (prop === Symbol.toStringTag || prop === Symbol.toPrimitive) { return `HttpClient: ${base}`; } - if (typeof prop !== "string") { throw new TypeError(`HttpClient: Uknown path ${typeof prop}`); } - const [method, path] = prop.split(" "); - // @ts-expect-error if not inside, throws if (!HTTP_VERBS.has(method)) { throw new TypeError(`HttpClient: Verb ${method} is not allowed`); } - return ( params: Record<string, string | number | string[] | number[]>, init?: RequestInit, ) => { const mapped = new Map(Object.entries(params)); - const compiled = path .split("/") .flatMap((segment) => { const isTemplate = segment.at(0) === ":" || segment.at(0) === "*"; const isRequred = segment.at(-1) !== "?"; - if (!isTemplate) { return segment; } - const name = segment.slice(1, !isRequred ? -1 : undefined); const param = mapped.get(name); mapped.delete(name); - if (param === undefined && isRequred) { throw new TypeError(`HttpClient: Missing ${name} at ${path}`); } - return param; }) .filter((x) => typeof x === "string" || typeof x === "number") .join("/"); - const url = new URL(compiled, base); - mapped.forEach((value, key) => { - if (value === undefined) return; - + if (value === undefined) { + return; + } const arrayed = Array.isArray(value) ? value : [value]; arrayed.forEach((item) => url.searchParams.append(key, `${item}`)); }); - const isJSON = init?.body != null && typeof init.body !== "string" && !(init.body instanceof ReadableStream) && @@ -145,13 +138,10 @@ export const createHttpClient = <T>({ !(init.body instanceof URLSearchParams) && !(init.body instanceof Blob) && !(init.body instanceof ArrayBuffer); - const headers = new Headers(init?.headers); defaultHeaders?.forEach((value, key) => headers.set(key, value)); isJSON && headers.set("content-type", "application/json"); - const body = isJSON ? JSON.stringify(init.body) : init?.body; - return fetcher(url.href, { ...init, headers: processHeaders(headers), @@ -162,12 +152,10 @@ export const createHttpClient = <T>({ }, }); }; - // deno-lint-ignore no-explicit-any export const nullOnNotFound = (error: any) => { if (error.status === 404) { return null; } - throw error; }; diff --git a/utils/weakcache.ts b/utils/weakcache.ts new file mode 100644 index 000000000..59ba9e877 --- /dev/null +++ b/utils/weakcache.ts @@ -0,0 +1 @@ +export * as weakcache from "npm:weak-lru-cache@1.0.0"; diff --git a/verified-reviews/mod.ts b/verified-reviews/mod.ts index 5250675f4..752b0fc78 100644 --- a/verified-reviews/mod.ts +++ b/verified-reviews/mod.ts @@ -1,13 +1,12 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; +import { type App, type AppContext as AC } from "@deco/deco"; export interface ConfigVerifiedReviews { idWebsite: string; secretKey?: Secret; plateforme?: string; } - /** * @title Verified Reviews * @description A specialized solution in the collection of customer reviews @@ -19,9 +18,7 @@ export default function App( ): App<Manifest, ConfigVerifiedReviews> { return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/verified-reviews/utils/client.ts b/verified-reviews/utils/client.ts index c08dbfa32..4da18103a 100644 --- a/verified-reviews/utils/client.ts +++ b/verified-reviews/utils/client.ts @@ -2,10 +2,8 @@ import { fetchAPI } from "../../utils/fetch.ts"; import { Ratings, Reviews, VerifiedReviewsFullReview } from "./types.ts"; import { Product } from "../../commerce/types.ts"; import { ConfigVerifiedReviews } from "../mod.ts"; -import { context } from "deco/live.ts"; - +import { context } from "@deco/deco"; export type ClientVerifiedReviews = ReturnType<typeof createClient>; - export interface PaginationOptions { count?: number; offset?: number; @@ -16,7 +14,6 @@ export interface PaginationOptions { | "rate_ASC" | "helpfulrating_DESC"; } - const MessageError = { ratings: "🔴⭐ Error on call ratings of Verified Review - probably unidentified product", @@ -25,29 +22,27 @@ const MessageError = { fullReview: "🔴⭐ Error on call Full Review of Verified Review - probably unidentified product", }; - const baseUrl = "https://awsapis3.netreviews.eu/product"; - export const createClient = (params: ConfigVerifiedReviews | undefined) => { - if (!params) return; - + if (!params) { + return; + } const { idWebsite } = params; - /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#338f8f1b-4379-40a2-8893-080fe5234679 */ - const rating = async ({ productId }: { productId: string }) => { + const rating = async ({ productId }: { + productId: string; + }) => { const payload = { query: "average", products: [productId], idWebsite: idWebsite, plateforme: "br", }; - try { const data = await fetchAPI<Ratings>(`${baseUrl}`, { method: "POST", body: JSON.stringify(payload), }); - return Object.keys(data).length ? data : undefined; } catch (error) { if (context.isDeploy) { @@ -58,9 +53,10 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { return undefined; } }; - /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#6d8ab05a-28b6-48b3-9e8f-6bbbc046619a */ - const ratings = async ({ productsIds }: { productsIds: string[] }) => { + const ratings = async ({ productsIds }: { + productsIds: string[]; + }) => { const payload = { query: "average", products: productsIds, @@ -72,7 +68,6 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { method: "POST", body: JSON.stringify(payload), }); - return Object.keys(data).length ? data : undefined; } catch (error) { if (context.isDeploy) { @@ -84,16 +79,14 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { return undefined; } }; - /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#daf51360-c79e-451a-b627-33bdd0ef66b8 */ - const reviews = ({ - productId, - count = 5, - offset = 0, - order = "date_desc", - }: PaginationOptions & { - productId: string; - }) => { + const reviews = ( + { productId, count = 5, offset = 0, order = "date_desc" }: + & PaginationOptions + & { + productId: string; + }, + ) => { const payload = { query: "reviews", product: productId, @@ -103,31 +96,25 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { limit: count, order: order, }; - return fetchAPI<Reviews>(`${baseUrl}`, { method: "POST", body: JSON.stringify(payload), }); }; - - const fullReview = async ({ - productId, - count = 5, - offset = 0, - }: PaginationOptions & { - productId: string; - }): Promise<VerifiedReviewsFullReview> => { + const fullReview = async ( + { productId, count = 5, offset = 0 }: PaginationOptions & { + productId: string; + }, + ): Promise<VerifiedReviewsFullReview> => { try { const response = await Promise.all([ rating({ productId }), reviews({ productId, count, offset }), ]); - const [responseRating, responseReview] = response.flat() as [ Ratings, Reviews | null, ]; - const currentRating = responseRating?.[productId]?.[0]; return { aggregateRating: currentRating @@ -169,7 +156,6 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { }; } }; - const storeReview = async (): Promise<Reviews["reviews"] | null> => { try { const response = await fetchAPI<Reviews["reviews"]>( @@ -178,10 +164,7 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { method: "GET", }, ); - - return ( - response ? response : [] - ); + return (response ? response : []); } catch (error) { if (context.isDeploy) { console.error(MessageError.ratings, error); @@ -191,7 +174,6 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { return null; } }; - return { rating, ratings, @@ -200,6 +182,5 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { storeReview, }; }; - export const getProductId = (product: Product) => product.isVariantOf!.productGroupID; diff --git a/vnda/actions/cart/simulation.ts b/vnda/actions/cart/simulation.ts index 22f0f65f4..ff7c7ee67 100644 --- a/vnda/actions/cart/simulation.ts +++ b/vnda/actions/cart/simulation.ts @@ -1,13 +1,11 @@ import { AppContext } from "../../mod.ts"; import type { ShippingMethod } from "../../utils/client/types.ts"; -import { badRequest } from "deco/mod.ts"; - +import { badRequest } from "@deco/deco"; export interface Props { skuId: string; quantity: number; zip: string; } - const action = async ( props: Props, _req: Request, @@ -15,7 +13,6 @@ const action = async ( ): Promise<ShippingMethod[]> => { const { api } = ctx; const { skuId, quantity, zip } = props; - if (!skuId || !quantity || !zip) { badRequest({ message: "could not find some props", @@ -28,5 +25,4 @@ const action = async ( }); return cep.json(); }; - export default action; diff --git a/vnda/mod.ts b/vnda/mod.ts index 4b07d5658..ad4285a1a 100644 --- a/vnda/mod.ts +++ b/vnda/mod.ts @@ -1,4 +1,3 @@ -import type { App, AppMiddlewareContext as AMC, FnContext } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { createHttpClient } from "../utils/http.ts"; import { PreviewContainer } from "../utils/preview.tsx"; @@ -6,11 +5,13 @@ import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { middleware } from "./middleware.ts"; import { OpenAPI } from "./utils/openapi/vnda.openapi.gen.ts"; - +import { + type App, + type AppMiddlewareContext as AMC, + type FnContext, +} from "@deco/deco"; export type AppMiddlewareContext = AMC<ReturnType<typeof VNDA>>; - export type AppContext = FnContext<State, Manifest>; - /** @title VNDA */ export interface Props { /** @@ -19,41 +20,33 @@ export interface Props { * @default deco */ account: string; - /** * @title Public store URL * @description Domain that is registered on VNDA * @default www.mystore.com.br */ publicUrl: string; - /** * @description The token generated from admin panel. Read here: https://developers.vnda.com.br/docs/chave-de-acesso-e-requisicoes. Do not add any other permissions than catalog. */ authToken: Secret; - /** * @title Use Sandbox * @description Define if sandbox environment should be used */ sandbox: boolean; - /** * @description Use VNDA as backend platform * @hide true */ platform: "vnda"; - /** @description Here is to put the pathname of the Search Page. Ex: /s. We have default values: "/busca" or "/s" */ searchPagePath?: string; } - export interface State extends Props { api: ReturnType<typeof createHttpClient<OpenAPI>>; } - export const color = 0x0C29D0; - /** * @title VNDA * @description Loaders, actions and workflows for adding VNDA Commerce Platform to your website. @@ -76,19 +69,16 @@ export default function VNDA(props: Props): App<Manifest, State> { ? "https://api.sandbox.vnda.com.br" : "https://api.vnda.com.br", }); - return { state: { ...props, api }, manifest, middleware, }; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/vnda/runtime.ts b/vnda/runtime.ts index 41d65a98d..da42a2435 100644 --- a/vnda/runtime.ts +++ b/vnda/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/vtex/components/VTEXPortalDataLayerCompatibility.tsx b/vtex/components/VTEXPortalDataLayerCompatibility.tsx index d0b962ac6..d9ca176d3 100644 --- a/vtex/components/VTEXPortalDataLayerCompatibility.tsx +++ b/vtex/components/VTEXPortalDataLayerCompatibility.tsx @@ -1,7 +1,6 @@ -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import { type JSX } from "preact"; import { Product } from "../../commerce/types.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; declare global { interface Window { // deno-lint-ignore no-explicit-any @@ -11,9 +10,7 @@ declare global { dataLayer: unknown[]; } } - type ScriptProps = JSX.HTMLAttributes<HTMLScriptElement>; - function addVTEXPortalDataSnippet(accountName: string) { performance.mark("start-vtex-dl"); const url = new URL(globalThis.window.location.href); @@ -25,33 +22,32 @@ function addVTEXPortalDataSnippet(accountName: string) { structuredDataScripts.forEach((v: any) => { structuredDatas.push(JSON.parse(v.text)); }); - const breadcrumbSD = structuredDatas.find( - (s) => s["@type"] === "BreadcrumbList", + const breadcrumbSD = structuredDatas.find((s) => + s["@type"] === "BreadcrumbList" ); performance.mark("end-sd"); - // deno-lint-ignore no-explicit-any const getPageType = (structuredData: undefined | Record<string, any>) => { - if (url.pathname === "/") return "homeView"; - + if (url.pathname === "/") { + return "homeView"; + } const isProductPage = structuredDatas.some((s) => s["@type"] === "Product"); - if (isProductPage) return "productView"; - + if (isProductPage) { + return "productView"; + } const isSearchPage = url.pathname === "/s"; - if (isSearchPage) return "internalSiteSearchView"; - + if (isSearchPage) { + return "internalSiteSearchView"; + } if (structuredData?.itemList?.length === 1) { return "departmentView"; } - if (structuredData?.itemList?.length >= 2) { return "categoryView"; } - return "otherView"; }; const pageType = getPageType(breadcrumbSD); - // deno-lint-ignore no-explicit-any const props: Record<string, any> = { pageCategory: "Home", @@ -64,7 +60,6 @@ function addVTEXPortalDataSnippet(accountName: string) { pageFacets: [], shelfProductIds: [], }; - const department = breadcrumbSD?.itemListElement?.[0]; if (pageType === "productView") { props.pageCategory = "Product"; @@ -72,33 +67,27 @@ function addVTEXPortalDataSnippet(accountName: string) { const product = globalThis.window.datalayer_product || {}; Object.assign(props, product); } - if (pageType === "departmentView") { props.pageCategory = "Department"; props.pageDepartment = department?.name || null; props.departmentName = department?.name || null; props.categoryName = department?.name || null; } - const category = breadcrumbSD?.itemListElement?.[1]; if (pageType === "categoryView") { props.pageCategory = "Category"; props.pageDepartment = department?.name || null; props.categoryName = category?.name || null; } - if (pageType === "internalSiteSearchView") { props.pageCategory = "InternalSiteSearch"; props.siteSearchTerm = url.searchParams.get("q"); } - if (pageType === "otherView") { const pathNames = url.pathname.split("/").filter(Boolean); props.pageCategory = pathNames.pop() || null; } - props.shelfProductIds = globalThis.window.shelfProductIds || []; - globalThis.window.dataLayer = globalThis.window.dataLayer || []; // VTEX Default position is first... globalThis.window.dataLayer.unshift(props); @@ -109,14 +98,12 @@ function addVTEXPortalDataSnippet(accountName: string) { performance.measure("vtex-dl-qs-ld-json", "start-vtex-dl", "end-sd"); performance.measure("vtex-dl-compat", "start-vtex-dl", "end-vtex-dl"); } - interface AddVTEXPortalData extends ScriptProps { accountName: string; } -export function AddVTEXPortalData({ - accountName, - ...props -}: AddVTEXPortalData) { +export function AddVTEXPortalData( + { accountName, ...props }: AddVTEXPortalData, +) { return ( <script {...props} @@ -126,23 +113,19 @@ export function AddVTEXPortalData({ /> ); } - interface ProductDetailsTemplateProps extends ScriptProps { product: Product; } - -export function ProductDetailsTemplate({ - product, - ...props -}: ProductDetailsTemplateProps) { - const departament = product.additionalProperty?.find( - (p) => p.name === "category", +export function ProductDetailsTemplate( + { product, ...props }: ProductDetailsTemplateProps, +) { + const departament = product.additionalProperty?.find((p) => + p.name === "category" ); const category = product.additionalProperty ?.slice() .reverse() .find((p) => p.name === "category"); - const offers = product.offers?.offers; const lowestOffer = offers?.[0]?.priceSpecification; const highestOffer = offers?.[offers.length - 1]?.priceSpecification; @@ -171,7 +154,6 @@ export function ProductDetailsTemplate({ sellerId: offers?.map(({ seller }) => seller)?.[0], sellerIds: offers?.map(({ seller }) => seller), }; - return ( <script {...props} @@ -182,13 +164,13 @@ export function ProductDetailsTemplate({ /> ); } - interface ProductInfoProps extends ScriptProps { product: Product; } - export function ProductInfo({ product, ...props }: ProductInfoProps) { - if (!product.isVariantOf?.productGroupID) return null; + if (!product.isVariantOf?.productGroupID) { + return null; + } return ( <script {...props} @@ -202,7 +184,6 @@ export function ProductInfo({ product, ...props }: ProductInfoProps) { /> ); } - export interface ProductSKUJsonProps extends ScriptProps { product: unknown; } diff --git a/vtex/loaders/collections/list.ts b/vtex/loaders/collections/list.ts index 84709fc8b..7c2f52bf8 100644 --- a/vtex/loaders/collections/list.ts +++ b/vtex/loaders/collections/list.ts @@ -1,11 +1,9 @@ -import { allowCorsFor } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; import { CollectionList } from "../../utils/types.ts"; - +import { allowCorsFor } from "@deco/deco"; export interface Props { term?: string; } - export default async function loader( { term }: Props, req: Request, @@ -14,31 +12,24 @@ export default async function loader( Object.entries(allowCorsFor(req)).map(([name, value]) => { ctx.response.headers.set(name, value); }); - const { vcs } = ctx; - const collectionResponse = term - ? await vcs - ["GET /api/catalog_system/pvt/collection/search/:searchTerms"]({ - searchTerms: term, - page: 1, - pageSize: 15, - }) - : await vcs - ["GET /api/catalog_system/pvt/collection/search"]({ - page: 1, - pageSize: 3000, - orderByAsc: false, - }); - + ? await vcs["GET /api/catalog_system/pvt/collection/search/:searchTerms"]({ + searchTerms: term, + page: 1, + pageSize: 15, + }) + : await vcs["GET /api/catalog_system/pvt/collection/search"]({ + page: 1, + pageSize: 3000, + orderByAsc: false, + }); const collectionList = await collectionResponse.json(); - const stringList = (collectionList as CollectionList)?.items?.map(( collection, ) => ({ value: `${collection.id}`, label: `${collection.id} - ${collection.name}`, })); - return stringList; } diff --git a/vtex/loaders/intelligentSearch/productListingPage.ts b/vtex/loaders/intelligentSearch/productListingPage.ts index 400008ee3..fcf1f311c 100644 --- a/vtex/loaders/intelligentSearch/productListingPage.ts +++ b/vtex/loaders/intelligentSearch/productListingPage.ts @@ -35,18 +35,15 @@ import type { } from "../../utils/types.ts"; import { getFirstItemAvailable } from "../legacy/productListingPage.ts"; import PLPDefaultPath from "../paths/PLPDefaultPath.ts"; -import { redirect } from "deco/mod.ts"; - +import { redirect } from "@deco/deco"; /** this type is more friendly user to fuzzy type that is 0, 1 or auto. */ export type LabelledFuzzy = "automatic" | "disabled" | "enabled"; - /** * VTEX Intelligent Search doesn't support pagination above 50 pages. * * We're now showing results for the last page so the page doesn't crash */ const VTEX_MAX_PAGES = 50; - const sortOptions = [ { value: "", label: "relevance:desc" }, { value: "price:desc", label: "price:desc" }, @@ -57,7 +54,6 @@ const sortOptions = [ { value: "release:desc", label: "release:desc" }, { value: "discount:desc", label: "discount:desc" }, ]; - const LEGACY_TO_IS: Record<string, Sort> = { OrderByPriceDESC: "price:desc", OrderByPriceASC: "price:asc", @@ -66,7 +62,6 @@ const LEGACY_TO_IS: Record<string, Sort> = { OrderByReleaseDateDESC: "release:desc", OrderByBestDiscountDESC: "discount:desc", }; - export const mapLabelledFuzzyToFuzzy = ( labelledFuzzy?: LabelledFuzzy, ): Fuzzy | undefined => { @@ -81,7 +76,6 @@ export const mapLabelledFuzzyToFuzzy = ( return; } }; - const ALLOWED_PARAMS = new Set([ "ps", "sort", @@ -91,7 +85,6 @@ const ALLOWED_PARAMS = new Set([ "fuzzy", "map", ]); - export interface Props { /** * @description overides the query term @@ -102,53 +95,44 @@ export interface Props { * @description number of products per page to display */ count: number; - /** * @title Sorting */ sort?: Sort; - /** * @title Fuzzy */ fuzzy?: LabelledFuzzy; - /** * @title Selected Facets * @description Override selected facets from url */ selectedFacets?: SelectedFacet[]; - /** * @title Hide Unavailable Items * @description Do not return out of stock items */ hideUnavailableItems?: boolean; - /** * @title Starting page query parameter offset. * @description Set the starting page offset. Default to 1. */ pageOffset?: number; - /** * @title Page query parameter */ page?: number; - /** * @description Include similar products * @deprecated Use product extensions instead */ similars?: boolean; - /** * @hide true * @description The URL of the page, used to override URL from request */ pageHref?: string; } - const searchArgsOf = (props: Props, url: URL) => { const hideUnavailableItems = props.hideUnavailableItems; const countFromSearchParams = url.searchParams.get("PS"); @@ -172,7 +156,6 @@ const searchArgsOf = (props: Props, url: URL) => { ); const fuzzy = mapLabelledFuzzyToFuzzy(props.fuzzy) ?? (url.searchParams.get("fuzzy") as Fuzzy); - return { query, fuzzy, @@ -183,7 +166,6 @@ const searchArgsOf = (props: Props, url: URL) => { selectedFacets, }; }; - const PAGE_TYPE_TO_MAP_PARAM = { Brand: "brand", Collection: "productClusterIds", @@ -193,50 +175,42 @@ const PAGE_TYPE_TO_MAP_PARAM = { NotFound: null, FullText: null, }; - const pageTypeToMapParam = (type: PageType["pageType"], index: number) => { if (type === "Category" || type === "Department" || type === "SubCategory") { return `category-${index + 1}`; } - return PAGE_TYPE_TO_MAP_PARAM[type]; }; - const queryFromPathname = ( isInSeachFormat: boolean, pageTypes: PageType[], path: string, ) => { const pathList = path.split("/").slice(1); - const isPage = Boolean(pageTypes.length); const isValidPathSearch = pathList.length == 1; - if (!isPage && !isInSeachFormat && isValidPathSearch) { // decode uri parse uri enconde symbols like '%20' to ' ' return decodeURI(pathList[0]); } }; - const filtersFromPathname = (pages: PageType[]) => pages .map((page, index) => { const key = pageTypeToMapParam(page.pageType, index); - if (!key || !page.name) { return; } - - return ( - key && + return (key && page.name && { - key, - value: slugify(page.name), - } - ); + key, + value: slugify(page.name), + }); }) - .filter((facet): facet is { key: string; value: string } => Boolean(facet)); - + .filter((facet): facet is { + key: string; + value: string; + } => Boolean(facet)); // Search API does not return the selected price filter, so there is no way for the // user to remove this price filter after it is set. This function selects the facet // so users can clear the price filters @@ -246,11 +220,11 @@ const selectPriceFacet = (facets: Facet[], selectedFacets: SelectedFacet[]) => { .filter((k) => k.key === "price") .map((s) => parseRange(s.value)) .filter(Boolean); - if (price) { for (const range of ranges) { - if (!range) continue; - + if (!range) { + continue; + } for (const val of price.values) { if (val.range.from === range.from && val.range.to === range.to) { val.selected = true; @@ -258,10 +232,8 @@ const selectPriceFacet = (facets: Facet[], selectedFacets: SelectedFacet[]) => { } } } - return facets; }; - /** * @title VTEX Integration - Intelligent Search * @description Product Listing Page loader @@ -276,63 +248,46 @@ const loader = async ( const url = new URL(props.pageHref || baseUrl); const segment = getSegmentFromBag(ctx); const currentPageoffset = props.pageOffset ?? 1; - const { - selectedFacets: baseSelectedFacets, - page, - ...args - } = searchArgsOf(props, url); - + const { selectedFacets: baseSelectedFacets, page, ...args } = searchArgsOf( + props, + url, + ); let pathToUse = url.href.replace(url.origin, ""); - if (pathToUse === "/" || pathToUse === "/*") { const result = await PLPDefaultPath({ level: 1 }, req, ctx); pathToUse = result?.possiblePaths[0] ?? pathToUse; } - const pageTypesPromise = pageTypesFromUrl(pathToUse, ctx); const allPageTypes = await pageTypesPromise; - const pageTypes = getValidTypesFromPageTypes(allPageTypes); - const selectedFacets = baseSelectedFacets.length === 0 ? filtersFromPathname(pageTypes) : baseSelectedFacets; - const selected = withDefaultFacets(selectedFacets, ctx); const fselected = selected.filter((f) => f.key !== "price"); - const isInSeachFormat = Boolean(selected.length) || Boolean(args.query); - const pathQuery = queryFromPathname(isInSeachFormat, pageTypes, url.pathname); - const searchArgs = { ...args, query: args.query || pathQuery }; - if (!isInSeachFormat && !pathQuery) { return null; } - const params = withDefaultParams({ ...searchArgs, page }); - // search products on VTEX. Feel free to change any of these parameters const [productsResult, facetsResult] = await Promise.all([ - vcsDeprecated[ - "GET /api/io/_v/api/intelligent-search/product_search/*facets" - ]( - { + vcsDeprecated + ["GET /api/io/_v/api/intelligent-search/product_search/*facets"]({ ...params, facets: toPath(selected), - }, - { ...STALE, headers: segment ? withSegmentCookie(segment) : undefined }, - ).then((res) => res.json()), - vcsDeprecated["GET /api/io/_v/api/intelligent-search/facets/*facets"]( - { - ...params, - facets: toPath(fselected), - }, - { ...STALE, headers: segment ? withSegmentCookie(segment) : undefined }, - ).then((res) => res.json()), + }, { + ...STALE, + headers: segment ? withSegmentCookie(segment) : undefined, + }).then((res) => res.json()), + vcsDeprecated["GET /api/io/_v/api/intelligent-search/facets/*facets"]({ + ...params, + facets: toPath(fselected), + }, { ...STALE, headers: segment ? withSegmentCookie(segment) : undefined }) + .then((res) => res.json()), ]); - // It is a feature from Intelligent Search on VTEX panel // redirect to a specific page based on configured rules if (productsResult.redirect) { @@ -341,7 +296,6 @@ const loader = async ( .href, ); } - /** Intelligent search API analytics. Fire and forget 🔫 */ const fullTextTerm = params["query"]; if (fullTextTerm) { @@ -363,14 +317,9 @@ const loader = async ( ) .catch(console.error); } - - const { - products: vtexProducts, - pagination, - recordsFiltered, - } = productsResult; + const { products: vtexProducts, pagination, recordsFiltered } = + productsResult; const facets = selectPriceFacet(facetsResult.facets, selectedFacets); - // Transform VTEX product format into schema.org's compatible format // If a property is missing from the final `products` array you can add // it in here @@ -386,31 +335,24 @@ const loader = async ( props.similars ? withIsSimilarTo(req, ctx, product) : product ), ); - const paramsToPersist = new URLSearchParams(); searchArgs.query && paramsToPersist.set("q", searchArgs.query); searchArgs.sort && paramsToPersist.set("sort", searchArgs.sort); const filters = facets .filter((f) => !f.hidden) .map(toFilter(selectedFacets, paramsToPersist)); - const itemListElement = pageTypesToBreadcrumbList(pageTypes, baseUrl); - const hasNextPage = Boolean(pagination.next.proxyUrl); const hasPreviousPage = page > 0; const nextPage = new URLSearchParams(url.searchParams); const previousPage = new URLSearchParams(url.searchParams); - if (hasNextPage) { nextPage.set("page", (page + currentPageoffset + 1).toString()); } - if (hasPreviousPage) { previousPage.set("page", (page + currentPageoffset - 1).toString()); } - const currentPage = page + currentPageoffset; - return { "@type": "ProductListingPage", breadcrumb: { @@ -436,15 +378,12 @@ const loader = async ( ), }; }; - export const cache = "stale-while-revalidate"; - export const cacheKey = (props: Props, req: Request, ctx: AppContext) => { const url = new URL(props.pageHref || req.url); if (url.searchParams.has("q")) { return null; } - const segment = getSegmentFromBag(ctx)?.token ?? ""; const params = new URLSearchParams([ ["query", props.query ?? ""], @@ -463,18 +402,14 @@ export const cacheKey = (props: Props, req: Request, ctx: AppContext) => { ], ["segment", segment], ]); - url.searchParams.forEach((value, key) => { - if (!ALLOWED_PARAMS.has(key.toLowerCase()) && !isFilterParam(key)) return; - + if (!ALLOWED_PARAMS.has(key.toLowerCase()) && !isFilterParam(key)) { + return; + } params.append(key, value); }); - params.sort(); - url.search = params.toString(); - return url.href; }; - export default loader; diff --git a/vtex/loaders/options/productIdByTerm.ts b/vtex/loaders/options/productIdByTerm.ts index 376df372d..5da5a1d85 100644 --- a/vtex/loaders/options/productIdByTerm.ts +++ b/vtex/loaders/options/productIdByTerm.ts @@ -1,37 +1,27 @@ -import { allowCorsFor } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; - +import { allowCorsFor } from "@deco/deco"; interface Props { term?: string; } - -const loader = async ( - props: Props, - req: Request, - ctx: AppContext, -) => { +const loader = async (props: Props, req: Request, ctx: AppContext) => { Object.entries(allowCorsFor(req)).map(([name, value]) => { ctx.response.headers.set(name, value); }); - const suggestions = await ctx.invoke.vtex.loaders.intelligentSearch .suggestions({ query: props.term || "", count: 10, }); - if (suggestions?.products?.length === 0) { return [{ value: "No products found, use search", label: "No products found, use search", }]; } - return suggestions?.products?.map((product) => ({ value: `${product.productID}`, label: `${product.productID} - ${product.name}`, image: product.image?.[0]?.url, })); }; - export default loader; diff --git a/vtex/mod.ts b/vtex/mod.ts index de20287b8..4d9f73cb1 100644 --- a/vtex/mod.ts +++ b/vtex/mod.ts @@ -1,10 +1,3 @@ -import type { - App as A, - AppContext as AC, - AppMiddlewareContext as AMC, - AppRuntime, - ManifestOf, -} from "deco/mod.ts"; import { createGraphqlClient } from "../utils/graphql.ts"; import { createHttpClient } from "../utils/http.ts"; import workflow from "../workflows/mod.ts"; @@ -20,12 +13,17 @@ import type { Secret } from "../website/loaders/secret.ts"; import { removeDirtyCookies } from "../utils/normalize.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { PreviewVtex } from "./preview/Preview.tsx"; - +import { + type App as A, + type AppContext as AC, + type AppMiddlewareContext as AMC, + type AppRuntime, + type ManifestOf, +} from "@deco/deco"; export type App = ReturnType<typeof VTEX>; export type AppContext = AC<App>; export type AppManifest = ManifestOf<App>; export type AppMiddlewareContext = AMC<App>; - export type SegmentCulture = Omit< Partial<Segment>, | "utm_medium" @@ -44,26 +42,22 @@ export interface Props { * @description VTEX Account name. For more info, read here: https://help.vtex.com/en/tutorial/o-que-e-account-name--i0mIGLcg3QyEy8OCicEoC. */ account: string; - /** * @title Public store URL * @description Domain that is registered on License Manager (e.g: www.mystore.com.br) */ publicUrl: string; - /** * @title App Key * @description Only required for extra features */ appKey?: Secret; - /** * @title App Token * @description Only required for extra features * @format password */ appToken?: Secret; - /** * @title Default Sales Channel * @description (Use defaultSegment instead) @@ -71,14 +65,11 @@ export interface Props { * @deprecated */ salesChannel?: string; - /** * @title Default Segment */ defaultSegment?: SegmentCulture; - usePortalSitemap?: boolean; - /** * @description Use VTEX as backend platform * @default vtex @@ -86,25 +77,17 @@ export interface Props { */ platform: "vtex"; } - export const color = 0xf71963; - /** * @title VTEX * @description Loaders, actions and workflows for adding VTEX Commerce Platform to your website. * @category Ecommmerce * @logo https://raw.githubusercontent.com/deco-cx/apps/main/vtex/logo.png */ -export default function VTEX({ - appKey, - appToken, - account, - publicUrl, - salesChannel, - ...props -}: Props) { +export default function VTEX( + { appKey, appToken, account, publicUrl, salesChannel, ...props }: Props, +) { const headers = new Headers(); - appKey && headers.set( "X-VTEX-API-AppKey", @@ -115,7 +98,6 @@ export default function VTEX({ "X-VTEX-API-AppToken", typeof appToken === "string" ? appToken : appToken?.get?.() ?? "", ); - const sp = createHttpClient<SP>({ base: `https://sp.vtex.com`, processHeaders: removeDirtyCookies, @@ -149,7 +131,6 @@ export default function VTEX({ processHeaders: removeDirtyCookies, headers: headers, }); - const state = { ...props, salesChannel: salesChannel ?? "1", @@ -162,17 +143,16 @@ export default function VTEX({ my, api, }; - - const app: A<Manifest, typeof state, [ReturnType<typeof workflow>]> = { + const app: A<Manifest, typeof state, [ + ReturnType<typeof workflow>, + ]> = { state, manifest, middleware, dependencies: [workflow({})], }; - return app; } - export const preview = async (props: AppRuntime) => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, diff --git a/vtex/preview/Preview.tsx b/vtex/preview/Preview.tsx index 33ebfc926..f55347d71 100644 --- a/vtex/preview/Preview.tsx +++ b/vtex/preview/Preview.tsx @@ -1,14 +1,10 @@ -import { BaseContext } from "deco/engine/core/resolver.ts"; -import { Context } from "deco/live.ts"; -import { AppRuntime } from "deco/types.ts"; import type { JSX } from "preact"; import { PreviewContainer } from "../../utils/preview.tsx"; import { App } from "../mod.ts"; - +import { type AppRuntime, type BaseContext, Context } from "@deco/deco"; export interface Props { publicUrl: string; } - export const PreviewVtex = ( app: AppRuntime<BaseContext, App["state"]> & { markdownContent: () => JSX.Element; @@ -19,7 +15,6 @@ export const PreviewVtex = ( const publicUrl = app.state?.publicUrl || ""; const account = app.state?.account || ""; const withoutSubDomain = publicUrl.split(".").slice(1).join("."); - return ( <PreviewContainer name="VTEX" @@ -56,7 +51,6 @@ export const PreviewVtex = ( /> ); }; - export function Indexing() { return ( <div> @@ -80,14 +74,11 @@ export function Indexing() { </div> ); } - -export function GoLivePtBr( - { decoSite, withoutSubDomain, account }: { - decoSite: string; - withoutSubDomain: string; - account: string; - }, -) { +export function GoLivePtBr({ decoSite, withoutSubDomain, account }: { + decoSite: string; + withoutSubDomain: string; + account: string; +}) { return ( <> <h2 class="text-2xl"> diff --git a/vtex/runtime.ts b/vtex/runtime.ts index 41d65a98d..da42a2435 100644 --- a/vtex/runtime.ts +++ b/vtex/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/vtex/sections/Analytics/Vtex.tsx b/vtex/sections/Analytics/Vtex.tsx index bd7f7a81d..53d75cf11 100644 --- a/vtex/sections/Analytics/Vtex.tsx +++ b/vtex/sections/Analytics/Vtex.tsx @@ -1,5 +1,3 @@ -import { SectionProps } from "deco/blocks/section.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import { AddToCartEvent, AnalyticsItem, @@ -8,19 +6,18 @@ import { import { AppContext } from "../../mod.ts"; import { getISCookiesFromBag } from "../../utils/intelligentSearch.ts"; import { SPEvent } from "../../utils/types.ts"; - +import { type SectionProps } from "@deco/deco"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; interface ISCookies { // deno-lint-ignore no-explicit-any anonymous: any; // deno-lint-ignore no-explicit-any session: any; } - const snippet = (account: string, agent: string, cookies: ISCookies | null) => { const url = new URL(window.location.href); const isSearch = url.searchParams.get("q"); const apiUrl = `https://sp.vtex.com/event-api/v1/${account}/event`; - const eventFetch = (props: SPEvent) => { fetch(apiUrl, { method: "POST", @@ -33,29 +30,25 @@ const snippet = (account: string, agent: string, cookies: ISCookies | null) => { }, }); }; - const baseProps = { agent, ...cookies, }; - // deno-lint-ignore no-explicit-any function isSelectItemEvent(event: any): event is SelectItemEvent { return event.name === "select_item"; } - // deno-lint-ignore no-explicit-any function isCartEvent(event: any): event is AddToCartEvent { const cartEvents = ["remove_from_cart", "add_to_cart", "view_cart"]; return cartEvents.includes(event.name); } - - type WithID<T> = T & { item_id: string }; - + type WithID<T> = T & { + item_id: string; + }; const hasItemId = <T,>(item: T): item is WithID<T> => // deno-lint-ignore no-explicit-any typeof (item as any).item_id === "string"; - // Session ping if (isSearch) { setInterval(() => { @@ -65,10 +58,12 @@ const snippet = (account: string, agent: string, cookies: ISCookies | null) => { }); }, 1000 * 60); } - globalThis.window.DECO.events.subscribe((event) => { if (isCartEvent(event)) { - const products: { productId: string; quantity: number }[] = []; + const products: { + productId: string; + quantity: number; + }[] = []; event.params?.items?.forEach((item: AnalyticsItem) => { if (hasItemId(item)) { products.push({ @@ -82,7 +77,6 @@ const snippet = (account: string, agent: string, cookies: ISCookies | null) => { products: products, }); } - if (isSelectItemEvent(event) && isSearch) { const [item] = event.params.items; if (hasItemId(item)) { @@ -97,7 +91,6 @@ const snippet = (account: string, agent: string, cookies: ISCookies | null) => { } }); }; - export default function VtexAnalytics( { account, agent, cookies }: SectionProps<typeof loader>, ) { @@ -109,10 +102,8 @@ export default function VtexAnalytics( /> ); } - export const loader = (_props: unknown, req: Request, ctx: AppContext) => { const cookies = getISCookiesFromBag(ctx); - return { account: ctx.account, agent: req.headers.get("user-agent") || "deco-sites/apps", diff --git a/vtex/workflows/events.ts b/vtex/workflows/events.ts index 5ab7ea2d9..ef556167b 100644 --- a/vtex/workflows/events.ts +++ b/vtex/workflows/events.ts @@ -1,14 +1,12 @@ -import type { Workflow } from "deco/blocks/workflow.ts"; -import type { WorkflowContext, WorkflowGen } from "deco/mod.ts"; import type { Product } from "../../commerce/types.ts"; import { waitForWorkflowCompletion } from "../../workflows/utils/awaiters.ts"; import type { VTEXNotificationPayload } from "../actions/trigger.ts"; import type { AppManifest } from "../mod.ts"; - +import { Workflow, WorkflowContext } from "@deco/deco/blocks"; +import { type WorkflowGen } from "@deco/deco"; interface Props { product: Workflow[]; } - export default function Index(props: Props) { return function* ( ctx: WorkflowContext<AppManifest>, @@ -17,41 +15,33 @@ export default function Index(props: Props) { if (!notification?.IdSku) { throw new Error(`Missing idSKU from notification`); } - const { IdSku, HasStockKeepingUnitRemovedFromAffiliate } = notification; - const product = yield ctx.invoke("vtex/loaders/workflow/product.ts", { productID: IdSku, }); - const action = HasStockKeepingUnitRemovedFromAffiliate || !product ? "DELETE" : "UPSERT"; - /** Start each registered workflow */ const workflows = props.product || []; - for (const workflow of workflows) { const p: Product = product || { "@type": "Product", sku: IdSku, productID: IdSku, }; - yield ctx.log( "Starting worflow for", workflow.key, "with productID", p.productID, ); - const exec = yield ctx.invoke("workflows/actions/start.ts", { // @ts-expect-error type is not resolving keys somehow key: workflow.key, props: workflow.props, args: [p, action], }); - if (exec.id && exec.status === "running") { yield waitForWorkflowCompletion(ctx, exec.id); } diff --git a/vtex/workflows/product/index.ts b/vtex/workflows/product/index.ts index 524d1180e..35f69d69d 100644 --- a/vtex/workflows/product/index.ts +++ b/vtex/workflows/product/index.ts @@ -1,34 +1,29 @@ -import { type WorkflowContext, WorkflowGen } from "deco/mod.ts"; import { type VTEXNotificationPayload } from "../../actions/trigger.ts"; import { type AppManifest } from "../../mod.ts"; import { type Product } from "../../../commerce/types.ts"; import { waitForWorkflowCompletion } from "../../../workflows/utils/awaiters.ts"; - +import { type WorkflowContext } from "@deco/deco/blocks"; +import { type WorkflowGen } from "@deco/deco"; const PAGE_SIZE = 50; - export default function Index() { return function* (ctx: WorkflowContext<AppManifest>): WorkflowGen<void> { let products: Product[] = []; let page = 1; let indexed = 0; - yield ctx.log("Starting product indexing workflow"); - do { yield ctx.log( `Product indexing workflow status: ${indexed} products were indexed`, ); - products = yield ctx.invoke("vtex/loaders/workflow/products.ts", { pagesize: PAGE_SIZE, page, }); - - if (!Array.isArray(products)) break; - + if (!Array.isArray(products)) { + break; + } page++; indexed += products.length; - const executions = []; for (const product of products) { const exec = yield ctx.invoke("workflows/actions/start.ts", { @@ -36,17 +31,14 @@ export default function Index() { key: "vtex-trigger", args: [{ IdSku: product.productID }] as VTEXNotificationPayload[], }); - executions.push(exec); } - for (const exec of executions) { if (exec.id && exec.status === "running") { yield waitForWorkflowCompletion(ctx, exec.id); } } } while (products.length > 0); - yield ctx.log( `Product indexing workflow finished after indexing ${indexed} skus`, ); diff --git a/wake/mod.ts b/wake/mod.ts index 08737ca65..487197940 100644 --- a/wake/mod.ts +++ b/wake/mod.ts @@ -1,4 +1,3 @@ -import type { App, FnContext } from "deco/mod.ts"; import { Markdown } from "../decohub/components/Markdown.tsx"; import { fetchSafe } from "../utils/fetch.ts"; import { createGraphqlClient } from "../utils/graphql.ts"; @@ -8,11 +7,9 @@ import type { Secret } from "../website/loaders/secret.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { CheckoutApi } from "./utils/client.ts"; import { OpenAPI } from "./utils/openapi/wake.openapi.gen.ts"; - +import { type App, type FnContext } from "@deco/deco"; export type AppContext = FnContext<State, Manifest>; - export let state: null | State = null; - /** @title Wake */ export interface Props { /** @@ -20,40 +17,33 @@ export interface Props { * @description erploja2 etc */ account: string; - /** * @title Checkout Url * @description https://checkout.erploja2.com.br */ checkoutUrl: string; - /** * @title Wake Storefront Token * @description https://wakecommerce.readme.io/docs/storefront-api-criacao-e-autenticacao-do-token */ storefrontToken: Secret; - /** * @title Wake API token * @description The token for accessing wake commerce */ token?: Secret; - /** * @description Use Wake as backend platform * @hide true */ platform: "wake"; } - export interface State extends Props { api: ReturnType<typeof createHttpClient<OpenAPI>>; checkoutApi: ReturnType<typeof createHttpClient<CheckoutApi>>; storefront: ReturnType<typeof createGraphqlClient>; } - export const color = 0xB600EE; - /** * @title Wake * @description Loaders, actions and workflows for adding Wake Commerce Platform to your website. @@ -62,51 +52,42 @@ export const color = 0xB600EE; */ export default function App(props: Props): App<Manifest, State> { const { token, storefrontToken, account, checkoutUrl } = props; - if (!token || !storefrontToken) { console.warn( "Missing tokens for wake app. Add it into the wake app config in deco.cx admin. Some functionalities may not work", ); } - // HEAD // const stringToken = typeof token === "string" ? token : token?.get?.() ?? ""; const stringStorefrontToken = typeof storefrontToken === "string" ? storefrontToken : storefrontToken?.get?.() ?? ""; - const api = createHttpClient<OpenAPI>({ base: "https://api.fbits.net", headers: new Headers({ "Authorization": `Basic ${stringToken}` }), fetcher: fetchSafe, }); - //22e714b360b7ef187fe4bdb93385dd0a85686e2a const storefront = createGraphqlClient({ endpoint: "https://storefront-api.fbits.net/graphql", headers: new Headers({ "TCS-Access-Token": `${stringStorefrontToken}` }), fetcher: fetchSafe, }); - const checkoutApi = createHttpClient<CheckoutApi>({ base: checkoutUrl ?? `https://${account}.checkout.fbits.store`, fetcher: fetchSafe, }); - state = { ...props, api, storefront, checkoutApi }; - return { state, manifest, }; } - export const preview = async () => { const markdownContent = await Markdown( new URL("./README.md", import.meta.url).href, ); - return { Component: PreviewContainer, props: { diff --git a/wake/runtime.ts b/wake/runtime.ts index 41d65a98d..da42a2435 100644 --- a/wake/runtime.ts +++ b/wake/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/wap/mod.ts b/wap/mod.ts index 55b1a05e1..a71d0ad25 100644 --- a/wap/mod.ts +++ b/wap/mod.ts @@ -1,18 +1,14 @@ -import type { App, FnContext } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { createHttpClient } from "../utils/http.ts"; import { OpenAPI } from "./utils/openapi/api.openapi.gen.ts"; import { fetchSafe } from "../utils/fetch.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type FnContext } from "@deco/deco"; export const color = 0xfe5000; - export interface State extends Props { api: ReturnType<typeof createHttpClient<OpenAPI>>; } - export type AppContext = FnContext<State, Manifest>; - /** @title Wap */ export interface Props { /** @@ -25,35 +21,28 @@ export interface Props { */ baseUrl: string; } - /** * @title Wap * @description Loaders, actions and workflows for adding Wap Commerce Platform to your website. * @category Ecommmerce */ -export default function App( - props: Props, -): App<Manifest, Props> { +export default function App(props: Props): App<Manifest, Props> { const headers = new Headers(); headers.set("accept", "application/json"); headers.set("content-type", "application/json"); headers.set("Cache-Control", "no-cache"); headers.set("App-Token", "wapstore"); - const api = createHttpClient<OpenAPI>({ base: props.baseUrl, fetcher: fetchSafe, headers: headers, }); - const state = { ...props, api, }; - return { manifest, state }; } - export const preview = () => { return { Component: PreviewContainer, diff --git a/wap/runtime.ts b/wap/runtime.ts index 41d65a98d..da42a2435 100644 --- a/wap/runtime.ts +++ b/wap/runtime.ts @@ -1,4 +1,3 @@ -import { proxy } from "deco/clients/withManifest.ts"; import { Manifest } from "./manifest.gen.ts"; - +import { proxy } from "@deco/deco/web"; export const invoke = proxy<Manifest>(); diff --git a/weather/mod.ts b/weather/mod.ts index a9a724524..d30afd8fc 100644 --- a/weather/mod.ts +++ b/weather/mod.ts @@ -1,24 +1,18 @@ -import type { App, AppContext as AC } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type AppContext as AC } from "@deco/deco"; // deno-lint-ignore ban-types export type State = {}; - /** * @title Deco Weather * @description Vary your content based on the current weather of your visitors. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/weather/logo.png */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export type AppContext = AC<ReturnType<typeof App>>; - export const preview = () => { return { Component: PreviewContainer, diff --git a/website/actions/secrets/encrypt.ts b/website/actions/secrets/encrypt.ts index 0309184d1..a57946779 100644 --- a/website/actions/secrets/encrypt.ts +++ b/website/actions/secrets/encrypt.ts @@ -1,15 +1,11 @@ -import { ActionContext } from "deco/types.ts"; -import { allowCorsFor } from "deco/utils/http.ts"; import { encryptToHex } from "../../utils/crypto.ts"; - +import { type ActionContext, allowCorsFor } from "@deco/deco"; export interface Props { value: string; } - export interface SignedMessage { value: string; } - export default async function encrypt( { value }: Props, req: Request, diff --git a/website/components/Analytics.tsx b/website/components/Analytics.tsx index bf48608a4..e61d79b41 100644 --- a/website/components/Analytics.tsx +++ b/website/components/Analytics.tsx @@ -1,23 +1,19 @@ import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; -import { context } from "deco/live.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; +import { context } from "@deco/deco"; declare global { interface Window { dataLayer: unknown[]; } } - export const getGTMIdFromSrc = (src: string | undefined) => { const trackingId = src ? new URL(src).searchParams.get("id") : undefined; return trackingId; }; - interface TagManagerProps { trackingId: string; src?: string; } - export function GoogleTagManager(props: TagManagerProps) { const _isOnPremises = !!props.src; const hasTrackingId = "trackingId" in props; @@ -33,7 +29,6 @@ export function GoogleTagManager(props: TagManagerProps) { `/ns.html?id=${hasTrackingId ? props.trackingId : ""}`, hostname, ); - return ( <> <Head> @@ -60,7 +55,6 @@ j=d.createElement(s);j.async=true;j.src=i;f.parentNode.insertBefore(j,f); </> ); } - export function GTAG({ trackingId }: Pick<TagManagerProps, "trackingId">) { return ( <Head> @@ -81,7 +75,6 @@ gtag("config", '${trackingId}');`, </Head> ); } - /** * This function handles all ecommerce analytics events. * Add another ecommerce analytics modules here. @@ -96,7 +89,6 @@ const snippet = () => { ) { return; } - if (event.name === "deco") { globalThis.window.dataLayer.push({ event: event.name, @@ -104,7 +96,6 @@ const snippet = () => { }); return; } - globalThis.window.dataLayer.push({ ecommerce: null }); globalThis.window.dataLayer.push({ event: event.name, @@ -112,43 +103,34 @@ const snippet = () => { }); }); }; - export interface Props { /** * @description google tag manager container id. For more info: https://developers.google.com/tag-platform/tag-manager/web#standard_web_page_installation . */ trackingIds?: string[]; - /** * @title GA Measurement Ids * @label measurement id * @description the google analytics property measurement id. For more info: https://support.google.com/analytics/answer/9539598 */ googleAnalyticsIds?: string[]; - /** * @description custom url for serving google tag manager. */ src?: string; - /** * @description Disable forwarding events into dataLayer */ disableAutomaticEventPush?: boolean; } - -export default function Analytics({ - trackingIds, - src, - googleAnalyticsIds, - disableAutomaticEventPush, -}: Props) { +export default function Analytics( + { trackingIds, src, googleAnalyticsIds, disableAutomaticEventPush }: Props, +) { const isDeploy = !!context.isDeploy; // Prevent breacking change. Drop this in next major to only have // src: https://hostname // trackingId: GTM-ID const trackingId = getGTMIdFromSrc(src) ?? ""; - return ( <> {/* Add Tag Manager script during production only. To test it locally remove the condition */} diff --git a/website/components/Clickhouse.tsx b/website/components/Clickhouse.tsx index c1f3bed99..2e2007b2b 100644 --- a/website/components/Clickhouse.tsx +++ b/website/components/Clickhouse.tsx @@ -1,7 +1,6 @@ import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; import { encryptToHex } from "../utils/crypto.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; declare global { interface Window { navigation: { @@ -11,7 +10,6 @@ declare global { }; } } - interface Event { hostname: string; site_id: string | number; @@ -49,15 +47,12 @@ interface Event { browser: string; browser_version: string; } - const SERVICE_ENDPOINT = Deno.env.get("EVENT_COLLECTOR") ?? "https://juggler.deco.site/live/invoke/site/actions/sendEvent.ts"; - function getDailySalt(): string { const today = new Date(); return today.toISOString().slice(0, 10); } - export const generateUserId = async ( sitename: string, ipAddress: string, @@ -67,31 +62,25 @@ export const generateUserId = async ( const data = daily_salt + sitename + ipAddress + userAgent; return await encryptToHex(data); }; - export const generateSessionId = (): string => { return crypto.randomUUID(); }; - /** * This function handles all ecommerce analytics events. * Add another ecommerce analytics modules here. */ -const snippet = ( - { siteId, siteName, serviceEndpoint, userId, sessionId }: { - siteId?: number; - siteName?: string; - serviceEndpoint: string; - userId: string; - sessionId: string; - }, -) => { +const snippet = ({ siteId, siteName, serviceEndpoint, userId, sessionId }: { + siteId?: number; + siteName?: string; + serviceEndpoint: string; + userId: string; + sessionId: string; +}) => { const props: Record<string, string> = {}; - const trackPageview = () => globalThis.window.DECO.events.dispatch({ name: "pageview", }); - // Attach pushState and popState listeners const originalPushState = history.pushState; if (originalPushState) { @@ -102,12 +91,11 @@ const snippet = ( }; addEventListener("popstate", trackPageview); } - const truncate = (str: string) => `${str}`.slice(0, 990); - globalThis.window.DECO.events.subscribe((event) => { - if (!event || event.name !== "deco") return; - + if (!event || event.name !== "deco") { + return; + } if (event.params) { const { flags, page } = event.params; if (Array.isArray(flags)) { @@ -117,47 +105,56 @@ const snippet = ( } props["pageId"] = truncate(`${page.id}`); } - trackPageview(); })(); - globalThis.window.DECO.events.subscribe((event) => { - if (!event) return; - + if (!event) { + return; + } const { name, params } = event; - - if (name === "deco") return; - + if (name === "deco") { + return; + } const values = { ...props }; for (const key in params) { // @ts-expect-error somehow typescript bugs const value = params[key]; - if (value !== null && value !== undefined) { values[key] = truncate( typeof value !== "object" ? value : JSON.stringify(value), ); } } - // Funções auxiliares para capturar informações dinâmicas function getDeviceType() { const ua = navigator.userAgent; - if (/mobile/i.test(ua)) return "mobile"; - if (/tablet/i.test(ua)) return "tablet"; + if (/mobile/i.test(ua)) { + return "mobile"; + } + if (/tablet/i.test(ua)) { + return "tablet"; + } return "desktop"; } - function getBrowserName() { const ua = navigator.userAgent; - if (/chrome|crios|crmo/i.test(ua)) return "Chrome"; - if (/firefox|fxios/i.test(ua)) return "Firefox"; - if (/safari/i.test(ua)) return "Safari"; - if (/msie|trident/i.test(ua)) return "Internet Explorer"; - if (/edge|edgios|edga/i.test(ua)) return "Edge"; + if (/chrome|crios|crmo/i.test(ua)) { + return "Chrome"; + } + if (/firefox|fxios/i.test(ua)) { + return "Firefox"; + } + if (/safari/i.test(ua)) { + return "Safari"; + } + if (/msie|trident/i.test(ua)) { + return "Internet Explorer"; + } + if (/edge|edgios|edga/i.test(ua)) { + return "Edge"; + } return "Unknown"; } - function getBrowserVersion() { const ua = navigator.userAgent; const browser = getBrowserName(); @@ -181,17 +178,25 @@ const snippet = ( } return match ? match[1] : "Unknown"; } - function getOperatingSystem() { const ua = navigator.userAgent; - if (/windows/i.test(ua)) return "Windows"; - if (/macintosh|mac os x/i.test(ua)) return "Mac OS"; - if (/linux/i.test(ua)) return "Linux"; - if (/android/i.test(ua)) return "Android"; - if (/ios|iphone|ipad|ipod/i.test(ua)) return "iOS"; + if (/windows/i.test(ua)) { + return "Windows"; + } + if (/macintosh|mac os x/i.test(ua)) { + return "Mac OS"; + } + if (/linux/i.test(ua)) { + return "Linux"; + } + if (/android/i.test(ua)) { + return "Android"; + } + if (/ios|iphone|ipad|ipod/i.test(ua)) { + return "iOS"; + } return "Unknown"; } - function getOSVersion() { const os = getOperatingSystem(); const ua = navigator.userAgent; @@ -212,21 +217,24 @@ const snippet = ( } return match ? match[1].replace("_", ".") : "Unknown"; } - function getUrlParam(param: string) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); } - function getReferrerSource(referrer: string) { - if (!referrer) return "direct"; + if (!referrer) { + return "direct"; + } const referrerUrl = new URL(referrer); - if (referrerUrl.hostname.includes("google")) return "google"; - if (referrerUrl.hostname.includes("facebook")) return "facebook"; + if (referrerUrl.hostname.includes("google")) { + return "google"; + } + if (referrerUrl.hostname.includes("facebook")) { + return "facebook"; + } // Adicione outras fontes conforme necessário return "other"; } - const mock: Event = { hostname: globalThis.window.location.origin, site_id: siteId || "", @@ -248,10 +256,8 @@ const snippet = ( utm_campaign: getUrlParam("utm_campaign"), utm_content: getUrlParam("utm_content"), utm_term: getUrlParam("utm_term"), - referrer: document.referrer, referrer_source: getReferrerSource(document.referrer), // benchmark: plausible - ip_city: undefined, // get server side ip_continent: undefined, // get server side ip_country: undefined, // get server side @@ -260,42 +266,32 @@ const snippet = ( ip_timezone: undefined, // get server side ip_lat: undefined, // get server side ip_long: undefined, // get server side - screen_size: `${window.screen.width}x${window.screen.height}`, - device: getDeviceType(), operating_system: getOperatingSystem(), operating_system_version: getOSVersion(), browser: getBrowserName(), browser_version: getBrowserVersion(), }; - - fetch( - serviceEndpoint, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - event: mock, - }), + fetch(serviceEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", }, - ); + body: JSON.stringify({ + event: mock, + }), + }); }); - // first pageview trackPageview(); }; - -function Clickhouse( - { siteId, siteName, userId, sessionId }: { - siteId?: number; - siteName?: string; - userId: string; - sessionId: string; - }, -) { +function Clickhouse({ siteId, siteName, userId, sessionId }: { + siteId?: number; + siteName?: string; + userId: string; + sessionId: string; +}) { return ( <Head> <script @@ -312,5 +308,4 @@ function Clickhouse( </Head> ); } - export default Clickhouse; diff --git a/website/components/Events.tsx b/website/components/Events.tsx index 79eed1370..9329af5c9 100644 --- a/website/components/Events.tsx +++ b/website/components/Events.tsx @@ -1,11 +1,8 @@ import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; -import { DECO_SEGMENT } from "deco/mod.ts"; -import { Flag } from "deco/types.ts"; import { type AnalyticsEvent, type Deco } from "../../commerce/types.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; +import { DECO_SEGMENT, type Flag } from "@deco/deco"; type EventHandler = (event?: AnalyticsEvent) => void | Promise<void>; - interface EventsAPI { dispatch: (event: unknown) => void; subscribe: ( @@ -13,37 +10,36 @@ interface EventsAPI { options?: AddEventListenerOptions | boolean, ) => () => void; } - interface FeatureFlags { enableImageOptimization: boolean; } - declare global { interface Window { - DECO: { events: EventsAPI; featureFlags: FeatureFlags }; + DECO: { + events: EventsAPI; + featureFlags: FeatureFlags; + }; DECO_ANALYTICS: Record< string, // deno-lint-ignore no-explicit-any (action: string, eventType: string, props?: any) => void >; - DECO_SITES_STD: { sendAnalyticsEvent: (event: unknown) => void }; + DECO_SITES_STD: { + sendAnalyticsEvent: (event: unknown) => void; + }; } } - const ENABLE_IMAGE_OPTIMIZATION = Deno.env.get("ENABLE_IMAGE_OPTIMIZATION") !== "false"; - /** * This function handles all ecommerce analytics events. * Add another ecommerce analytics modules here. */ -const snippet = ( - { deco: { page }, segmentCookie, featureFlags }: { - deco: Deco; - segmentCookie: string; - featureFlags: FeatureFlags; - }, -) => { +const snippet = ({ deco: { page }, segmentCookie, featureFlags }: { + deco: Deco; + segmentCookie: string; + featureFlags: FeatureFlags; +}) => { const cookie = document.cookie; const out: Record<string, string> = {}; if (cookie !== null) { @@ -54,7 +50,6 @@ const snippet = ( out[key] = cookieVal.join("="); } } - const flags: Flag[] = []; if (out[segmentCookie]) { try { @@ -69,26 +64,19 @@ const snippet = ( console.error("Error parsing deco_segment cookie"); } } - const target = new EventTarget(); - const dispatch: EventsAPI["dispatch"] = (event: unknown) => { target.dispatchEvent(new CustomEvent("analytics", { detail: event })); }; - const subscribe: EventsAPI["subscribe"] = (handler, opts) => { // deno-lint-ignore no-explicit-any const cb = ({ detail }: any) => handler(detail); - handler({ name: "deco", params: { flags, page } }); - target.addEventListener("analytics", cb, opts); - return () => { target.removeEventListener("analytics", cb, opts); }; }; - globalThis.window.DECO_SITES_STD = { sendAnalyticsEvent: dispatch }; globalThis.window.DECO = { ...globalThis.window.DECO, @@ -96,8 +84,9 @@ const snippet = ( featureFlags, }; }; - -function Events({ deco }: { deco: Deco }) { +function Events({ deco }: { + deco: Deco; +}) { return ( <Head> <script @@ -114,5 +103,4 @@ function Events({ deco }: { deco: Deco }) { </Head> ); } - export default Events; diff --git a/website/components/_Controls.tsx b/website/components/_Controls.tsx index 25c2a3f8d..ec7918795 100644 --- a/website/components/_Controls.tsx +++ b/website/components/_Controls.tsx @@ -1,32 +1,28 @@ import { Head } from "$fresh/runtime.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; -import { context } from "deco/live.ts"; -import type { Flag, Site } from "deco/types.ts"; import { DomInspectorActivators } from "https://deno.land/x/inspect_vscode@0.2.1/inspector.ts"; import { DomInspector } from "https://deno.land/x/inspect_vscode@0.2.1/mod.ts"; import { Page } from "../../commerce/types.ts"; - +import { useScriptAsDataURI } from "@deco/deco/hooks"; +import { context, type Flag, type Site } from "@deco/deco"; const IS_LOCALHOST = context.deploymentId === undefined; - interface Live { page?: Page; site: Site; flags: Flag[]; avoidRedirectingToEditor?: boolean; } - interface Props { site: Site; page?: Page; flags?: Flag[]; avoidRedirectingToEditor?: boolean; } - type EditorEvent = { type: "editor::inject"; - args: { script: string }; + args: { + script: string; + }; }; - const domInspectorModule = IS_LOCALHOST ? ` const DomInspectorActivators = { @@ -37,66 +33,53 @@ const DomInspectorActivators = { }; ${DomInspector.toString()}` : ""; - const snippet = (live: Live) => { const onKeydown = (event: KeyboardEvent) => { // in case loaded in iframe, avoid redirecting to editor while in editor if (globalThis.window !== globalThis.window.parent) { return; } - // Disable going to admin while input it being typed if (event.target !== document.body) { return; } - if (event.defaultPrevented) { return; } - if ( (event.ctrlKey && event.shiftKey && event.key === "E") || event.key === "." ) { event.preventDefault(); event.stopPropagation(); - const pathname = `/choose-editor?site=${globalThis.window.LIVE.site.name}&domain=${globalThis.window.location.origin}&pageId=${globalThis.window.LIVE.page.id}`; - const href = new URL(pathname, "https://admin.deco.cx"); - href.searchParams.set( "path", encodeURIComponent( `${globalThis.window.location.pathname}${globalThis.window.location.search}`, ), ); - href.searchParams.set( "pathTemplate", encodeURIComponent(globalThis.window.LIVE.page.pathTemplate || "/*"), ); - if ((event.ctrlKey || event.metaKey) && event.key === ".") { globalThis.window.open(href, "_blank"); return; } - globalThis.window.location.href = `${href}`; } }; - const onMessage = (event: MessageEvent<EditorEvent>) => { const { data } = event; - switch (data.type) { case "editor::inject": { return eval(data.args.script); } } }; - //@ts-ignore: "DomInspector not available" const _inspector = typeof DomInspector !== "undefined" && //@ts-ignore: "DomInspector not available" @@ -107,21 +90,16 @@ const snippet = (live: Live) => { activator: DomInspectorActivators.Backquote, path: "/live/inspect", }); - /** Setup global variables */ globalThis.window.LIVE = { ...globalThis.window.LIVE, ...live }; - /** Setup listeners */ - if (!live.avoidRedirectingToEditor) { document.body.addEventListener("keydown", onKeydown); } // navigate to admin when user clicks ctrl+shift+e - // focus element when inside admin addEventListener("message", onMessage); }; - function LiveControls( { site, page, flags = [], avoidRedirectingToEditor }: Props, ) { @@ -144,5 +122,4 @@ function LiveControls( </Head> ); } - export default LiveControls; diff --git a/website/flags/audience.ts b/website/flags/audience.ts index 27f665a77..46efe33ae 100644 --- a/website/flags/audience.ts +++ b/website/flags/audience.ts @@ -1,9 +1,6 @@ -import { FlagObj } from "deco/blocks/flag.ts"; -import { Handler } from "deco/blocks/handler.ts"; -import { Matcher } from "deco/blocks/matcher.ts"; -import JsonViewer from "deco/components/JsonViewer.tsx"; -import { Resolvable } from "deco/engine/core/resolver.ts"; -import { metabasePreview } from "deco/utils/metabase.tsx"; +import { type Resolvable } from "@deco/deco"; +import { type FlagObj, type Handler, type Matcher } from "@deco/deco/blocks"; +import { JsonViewer, metabasePreview } from "@deco/deco/utils"; import Flag from "./flag.ts"; export { onBeforeResolveProps } from "./everyone.ts"; /** @@ -17,7 +14,9 @@ export interface Route { */ isHref?: boolean; // FIXME this should be placed at nested level 3 of the object to avoid being resolved before the routeSelection is executed. - handler: { value: Resolvable<Handler> }; + handler: { + value: Resolvable<Handler>; + }; /** * @title Priority * @description higher priority means that this route will be used in favor of other routes with less or none priority @@ -43,16 +42,13 @@ export interface Audience { name: string; routes?: Routes; } - /** * @title Audience * @description Select routes based on the matched audience. */ -export default function Audience({ - matcher, - routes, - name, -}: Audience): FlagObj<Route[]> { +export default function Audience( + { matcher, routes, name }: Audience, +): FlagObj<Route[]> { return Flag<Route[]>({ matcher, true: routes ?? [], @@ -61,7 +57,9 @@ export default function Audience({ }); } -export const preview = (result: unknown, ctx: { request: Request }) => { +export const preview = (result: unknown, ctx: { + request: Request; +}) => { const url = new URL(ctx.request.url); const metabaseUrl = url.searchParams.get("metabase"); return metabaseUrl ? metabasePreview(metabaseUrl) : { diff --git a/website/flags/everyone.ts b/website/flags/everyone.ts index 72aec582a..4b7cfaf57 100644 --- a/website/flags/everyone.ts +++ b/website/flags/everyone.ts @@ -1,29 +1,26 @@ -import { FlagObj } from "deco/blocks/flag.ts"; -import { asResolved } from "deco/engine/core/resolver.ts"; import Audience, { Route, Routes } from "./audience.ts"; import MatchAlways from "../matchers/always.ts"; - +import { type FlagObj } from "@deco/deco/blocks"; +import { asResolved } from "@deco/deco"; export interface EveryoneConfig { routes?: Routes; } - /** * @title Audience Everyone * @description Always match regardless of the current user */ -export default function Everyone( - { routes }: EveryoneConfig, -): FlagObj<Route[]> { +export default function Everyone({ routes }: EveryoneConfig): FlagObj<Route[]> { return Audience({ matcher: MatchAlways, routes: routes ?? [], name: "Everyone", }); } - -export const onBeforeResolveProps = <T extends { routes?: Routes }>( - props: T, -): T => { +export const onBeforeResolveProps = < + T extends { + routes?: Routes; + }, +>(props: T): T => { if (Array.isArray(props?.routes)) { const newRoutes: T = { ...props, routes: [] }; for (const route of (props?.routes ?? [])) { diff --git a/website/flags/flag.ts b/website/flags/flag.ts index 0d026cc8e..7e9b1a8d8 100644 --- a/website/flags/flag.ts +++ b/website/flags/flag.ts @@ -1,17 +1,12 @@ -import { FlagObj } from "deco/blocks/flag.ts"; +import { type FlagObj } from "@deco/deco/blocks"; export { onBeforeResolveProps } from "./everyone.ts"; - export type Props<T> = FlagObj<T>; - /** * @title Flag */ -export default function Flag<T>({ - matcher, - name, - true: T, - false: F, -}: Props<T>): FlagObj<T> { +export default function Flag<T>( + { matcher, name, true: T, false: F }: Props<T>, +): FlagObj<T> { return { matcher, true: T, diff --git a/website/flags/multivariate/image.ts b/website/flags/multivariate/image.ts index f5d095790..5234cc571 100644 --- a/website/flags/multivariate/image.ts +++ b/website/flags/multivariate/image.ts @@ -1,8 +1,7 @@ export { onBeforeResolveProps } from "../../utils/multivariate.ts"; -import { MultivariateFlag } from "deco/blocks/flag.ts"; import multivariate, { MultivariateProps } from "../../utils/multivariate.ts"; import { ImageWidget } from "../../../admin/widgets.ts"; - +import { type MultivariateFlag } from "@deco/deco/blocks"; /** * @title Image Variants */ diff --git a/website/flags/multivariate/message.ts b/website/flags/multivariate/message.ts index 1a1bd5bc2..b7cc6c6e5 100644 --- a/website/flags/multivariate/message.ts +++ b/website/flags/multivariate/message.ts @@ -1,9 +1,7 @@ export { onBeforeResolveProps } from "../../utils/multivariate.ts"; -import { MultivariateFlag } from "deco/blocks/flag.ts"; import multivariate, { MultivariateProps } from "../../utils/multivariate.ts"; - +import { type MultivariateFlag } from "@deco/deco/blocks"; export type Message = string; - /** * @title Message Variants */ diff --git a/website/flags/multivariate/page.ts b/website/flags/multivariate/page.ts index c323c34ba..945ba877b 100644 --- a/website/flags/multivariate/page.ts +++ b/website/flags/multivariate/page.ts @@ -1,8 +1,6 @@ export { onBeforeResolveProps } from "../../utils/multivariate.ts"; -import { MultivariateFlag } from "deco/blocks/flag.ts"; -import { Section } from "deco/blocks/section.ts"; import multivariate, { MultivariateProps } from "../../utils/multivariate.ts"; - +import { type MultivariateFlag, type Section } from "@deco/deco/blocks"; /** * @title Page Variants */ diff --git a/website/flags/multivariate/section.ts b/website/flags/multivariate/section.ts index 2fc6e9924..2d3557f03 100644 --- a/website/flags/multivariate/section.ts +++ b/website/flags/multivariate/section.ts @@ -1,8 +1,6 @@ export { onBeforeResolveProps } from "../../utils/multivariate.ts"; -import { MultivariateFlag } from "deco/blocks/flag.ts"; -import { Section } from "deco/blocks/section.ts"; import multivariate, { MultivariateProps } from "../../utils/multivariate.ts"; - +import { type MultivariateFlag, type Section } from "@deco/deco/blocks"; /** * @title Section Variants */ diff --git a/website/functions/requestToParam.ts b/website/functions/requestToParam.ts index f92c4af70..53c24acb1 100644 --- a/website/functions/requestToParam.ts +++ b/website/functions/requestToParam.ts @@ -1,8 +1,6 @@ -import type { FunctionContext, LoaderFunction } from "deco/types.ts"; - +import { type FunctionContext, type LoaderFunction } from "@deco/deco"; /** @title Force param */ export type RequestURLParam = string; - export interface Props { /** * @default slug @@ -12,17 +10,12 @@ export interface Props { */ param: string; } - /** * @title Get params from request parameters * @description Set param to slug for routes of type /:slug */ -const requestToParam: LoaderFunction< - Props, - RequestURLParam, - FunctionContext -> = (_req, ctx) => ({ - data: ctx.params[ctx.state.$live.param || "slug"], -}); - +const requestToParam: LoaderFunction<Props, RequestURLParam, FunctionContext> = + (_req, ctx) => ({ + data: ctx.params[ctx.state.$live.param || "slug"], + }); export default requestToParam; diff --git a/website/handlers/fresh.ts b/website/handlers/fresh.ts index 8a77109b7..53080da51 100644 --- a/website/handlers/fresh.ts +++ b/website/handlers/fresh.ts @@ -1,18 +1,17 @@ -import { HandlerContext } from "$fresh/server.ts"; -import { Page } from "deco/blocks/page.tsx"; -import { RequestContext } from "deco/deco.ts"; import { + allowCorsFor, asResolved, - BaseContext, + type BaseContext, + DecoSiteState, + type DecoState, isDeferred, -} from "deco/engine/core/resolver.ts"; -import { DecoState } from "deco/types.ts"; -import { allowCorsFor } from "deco/utils/http.ts"; + RequestContext, +} from "@deco/deco"; +import { type Page } from "@deco/deco/blocks"; import { getSetCookies } from "std/http/cookie.ts"; import { __DECO_FBT } from "../../utils/deferred.ts"; import { errorIfFrameworkMismatch } from "../../utils/framework.tsx"; import { AppContext } from "../mod.ts"; - type ConnInfo = Deno.ServeHandlerInfo; /** * @title Fresh Config @@ -20,18 +19,23 @@ type ConnInfo = Deno.ServeHandlerInfo; export interface FreshConfig { page: Page; } - -export const isFreshCtx = <TState>( - ctx: ConnInfo | HandlerContext<unknown, TState>, -): ctx is HandlerContext<unknown, TState> => { - return typeof (ctx as HandlerContext).render === "function"; +export interface RenderCtx { + render: (data: unknown) => Promise<Response>; +} +export const isHandlerContext = <TState = DecoSiteState>( + ctx: ConnInfo | RenderCtx, +): ctx is RenderCtx & { + state: TState; + params: Record<string, string>; +} => { + return typeof (ctx as RenderCtx).render === "function"; }; - function abortHandler(ctrl: AbortController, signal: AbortSignal) { let aborted = false; const abortCtrlInstance = () => { - if (aborted) return; // Early return if already handled - + if (aborted) { + return; // Early return if already handled + } try { if (!ctrl.signal.aborted) { ctrl?.abort(); @@ -45,15 +49,12 @@ function abortHandler(ctrl: AbortController, signal: AbortSignal) { }; return abortCtrlInstance; } - function registerFinilizer(req: Request, abortCtrl: () => void) { const finalizer = new FinalizationRegistry((abortCtrl: () => void) => { req.signal.removeEventListener("abort", abortCtrl); }); - finalizer.register(req, abortCtrl); } - /** * @title Fresh Page * @description Renders a fresh page. @@ -78,19 +79,13 @@ export default function Fresh( const url = new URL(req.url); const asJson = url.searchParams.get("asJson"); const delayFromProps = appContext.firstByteThresholdMS ? 1 : 0; - const delay = Number( - url.searchParams.get(__DECO_FBT) ?? delayFromProps, - ); - + const delay = Number(url.searchParams.get(__DECO_FBT) ?? delayFromProps); /** Controller to abort third party fetch (loaders) */ const ctrl = new AbortController(); const abortCtrl = abortHandler(ctrl, req.signal); - /** Aborts when: Incomming request is aborted */ req.signal.addEventListener("abort", abortCtrl, { once: true }); - registerFinilizer(req, abortCtrl); - /** * Aborts when: * @@ -101,28 +96,23 @@ export default function Fresh( const firstByteThreshold = !asJson && delay && !appContext.isBot ? delay === 1 ? ctrl.abort() : setTimeout(() => ctrl.abort(), delay) : undefined; - try { const getPage = RequestContext.bind( { signal: ctrl.signal }, async () => - isDeferred<Page, BaseContext & { context: ConnInfo }>( - freshConfig.page, - ) + isDeferred< + Page, + BaseContext & { + context: ConnInfo; + } + >(freshConfig.page) ? await freshConfig.page({ context: ctx }, { propagateOptions: true, hooks: { - onPropsResolveStart: ( - resolve, - _props, - resolver, - ) => { + onPropsResolveStart: (resolve, _props, resolver) => { let next = resolve; if (resolver?.type === "matchers") { // matchers should not have a timeout. - next = RequestContext.bind( - { signal: req.signal }, - resolve, - ); + next = RequestContext.bind({ signal: req.signal }, resolve); } return next(); }, @@ -130,7 +120,6 @@ export default function Fresh( }) : freshConfig.page, ); - const page = await appContext?.monitoring?.tracer?.startActiveSpan?.( "load-data", async (span) => { @@ -145,20 +134,16 @@ export default function Fresh( } }, ); - if (asJson !== null) { return Response.json(page, { headers: allowCorsFor(req) }); } - if (isFreshCtx<DecoState>(ctx)) { + if (isHandlerContext<DecoState>(ctx)) { const timing = appContext?.monitoring?.timings?.start?.( "render-to-string", ); - - const renderToString = RequestContext.bind( - { framework: appContext.flavor?.framework ?? "fresh" }, - ctx.render, - ); - + const renderToString = RequestContext.bind({ + framework: appContext.flavor?.framework ?? "fresh", + }, ctx.render); const response = await appContext.monitoring!.tracer.startActiveSpan( "render-to-string", async (span) => { @@ -192,7 +177,6 @@ export default function Fresh( } }, ); - return response; } return Response.json({ message: "Fresh is not being used" }, { @@ -205,10 +189,7 @@ export default function Fresh( } }; } - -export const onBeforeResolveProps = ( - props: FreshConfig, -) => { +export const onBeforeResolveProps = (props: FreshConfig) => { if (props?.page) { return { ...props, page: asResolved(props.page, true) }; } diff --git a/website/handlers/proxy.ts b/website/handlers/proxy.ts index c036a28d3..5620b05c1 100644 --- a/website/handlers/proxy.ts +++ b/website/handlers/proxy.ts @@ -1,9 +1,8 @@ -import { DecoSiteState } from "deco/mod.ts"; +import { type DecoSiteState } from "@deco/deco"; import { proxySetCookie } from "../../utils/cookie.ts"; import { removeDirtyCookies as removeDirtyCookiesFn } from "../../utils/normalize.ts"; import { Script } from "../types.ts"; -import { isFreshCtx } from "./fresh.ts"; - +import { isHandlerContext } from "./fresh.ts"; type Handler = Deno.ServeHandler; const HOP_BY_HOP = [ "Keep-Alive", @@ -15,7 +14,6 @@ const HOP_BY_HOP = [ "Proxy-Authorization", "Proxy-Authenticate", ]; - const noTrailingSlashes = (str: string) => str.at(-1) === "/" ? str.slice(0, -1) : str; const sanitize = (str: string) => str.startsWith("/") ? str : `/${str}`; @@ -26,7 +24,6 @@ export const removeCFHeaders = (headers: Headers) => { } }); }; - /** * @title {{{key}}} - {{{value}}} */ @@ -40,12 +37,10 @@ export interface Header { */ value: string; } - export interface TextReplace { from: string; to: string; } - export interface Props { /** * @description the proxy url. @@ -57,7 +52,6 @@ export interface Props { * @example /api */ basePath?: string; - /** * @description Host that should be used when proxying the request */ @@ -66,24 +60,19 @@ export interface Props { * @description custom headers */ customHeaders?: Header[]; - /** * @description Scripts to be included in the head of the html */ includeScriptsToHead?: { includes?: Script[]; }; - /** * @description follow redirects * @default 'manual' */ redirect?: "manual" | "follow"; - avoidAppendPath?: boolean; - replaces?: TextReplace[]; - /** * @description remove cookies that have non-ASCII characters and some symbols * @default false @@ -91,7 +80,6 @@ export interface Props { removeDirtyCookies?: boolean; excludeHeaders?: string[]; } - /** * @title Proxy * @description Proxies request to the target url. @@ -115,30 +103,24 @@ export default function Proxy({ const path = basePath && basePath.length > 0 ? url.pathname.replace(basePath, "") : url.pathname; - const to = new URL( `${proxyUrl}${avoidAppendPath ? "" : sanitize(path)}?${qs}`, ); - const headers = new Headers(req.headers); HOP_BY_HOP.forEach((h) => headers.delete(h)); - - if (isFreshCtx<DecoSiteState>(_ctx)) { + if (isHandlerContext<DecoSiteState>(_ctx)) { _ctx?.state?.monitoring?.logger?.log?.("proxy received headers", headers); } removeCFHeaders(headers); // cf-headers are not ASCII-compliant if (removeDirtyCookies) { removeDirtyCookiesFn(headers); } - - if (isFreshCtx<DecoSiteState>(_ctx)) { + if (isHandlerContext<DecoSiteState>(_ctx)) { _ctx?.state?.monitoring?.logger?.log?.("proxy sent headers", headers); } - headers.set("origin", req.headers.get("origin") ?? url.origin); headers.set("host", hostToUse ?? to.host); headers.set("x-forwarded-host", url.host); - for (const { key, value } of customHeaders) { if (key === "cookie") { const existingCookie = headers.get("cookie"); @@ -151,11 +133,9 @@ export default function Proxy({ headers.set(key, value); } } - for (const key of excludeHeaders) { headers.delete(key); } - const response = await fetch(to, { headers, redirect, @@ -163,11 +143,8 @@ export default function Proxy({ method: req.method, body: req.body, }); - const contentType = response.headers.get("Content-Type"); - let newBody: ReadableStream<Uint8Array> | string | null = response.body; - if ( contentType?.includes("text/html") && includeScriptsToHead?.includes && @@ -181,7 +158,6 @@ export default function Proxy({ // Split the response body at </head> position const beforeHeadEnd = newBody.substring(0, headEndPos); const afterHeadEnd = newBody.substring(headEndPos); - // Prepare scripts to insert let scriptsInsert = ""; for (const script of (includeScriptsToHead?.includes ?? [])) { @@ -189,30 +165,21 @@ export default function Proxy({ ? script.src : script.src(req); } - // Combine the new response body newBody = beforeHeadEnd + scriptsInsert + afterHeadEnd; } } - // Change cookies domain const responseHeaders = new Headers(response.headers); responseHeaders.delete("set-cookie"); - proxySetCookie(response.headers, responseHeaders, url); - if (response.status >= 300 && response.status < 400) { // redirect change location header const location = responseHeaders.get("location"); if (location) { - responseHeaders.set( - "location", - location.replace(proxyUrl, url.origin), - ); + responseHeaders.set("location", location.replace(proxyUrl, url.origin)); } } - let text: undefined | string = undefined; - if (replaces && replaces.length > 0) { if (response.ok) { text = await new Response(newBody).text(); @@ -221,13 +188,9 @@ export default function Proxy({ }); } } - - return new Response( - text || newBody, - { - status: response.status, - headers: responseHeaders, - }, - ); + return new Response(text || newBody, { + status: response.status, + headers: responseHeaders, + }); }; } diff --git a/website/handlers/redirect.ts b/website/handlers/redirect.ts index 21ed52136..8d31b27f2 100644 --- a/website/handlers/redirect.ts +++ b/website/handlers/redirect.ts @@ -1,4 +1,4 @@ -import { isFreshCtx } from "./fresh.ts"; +import { isHandlerContext } from "./fresh.ts"; type ConnInfo = Deno.ServeHandlerInfo; export interface RedirectConfig { @@ -24,7 +24,7 @@ export default function Redirect( }; return (req: Request, conn: ConnInfo) => { - const params = isFreshCtx(conn) ? conn.params ?? {} : {}; + const params = isHandlerContext(conn) ? conn.params ?? {} : {}; /** * This allows redirects to have dynamic parameters. * diff --git a/website/handlers/router.ts b/website/handlers/router.ts index fa8e44836..90d0d2ba9 100644 --- a/website/handlers/router.ts +++ b/website/handlers/router.ts @@ -1,51 +1,43 @@ -import { weakcache } from "deco/deps.ts"; -import { ResolveOptions } from "deco/engine/core/mod.ts"; import { + type DecoSiteState, + type DecoState, isDeferred, - Resolvable, + type Resolvable, ResolveFunc, -} from "deco/engine/core/resolver.ts"; -import { isAwaitable } from "deco/engine/core/utils.ts"; -import { RouteContext } from "deco/engine/manifest/manifest.ts"; -import { DecoSiteState, DecoState } from "deco/types.ts"; + ResolveOptions, + RouteContext, +} from "@deco/deco"; +import { isAwaitable } from "@deco/deco/utils"; +import { weakcache } from "../../utils/weakcache.ts"; import { Route, Routes } from "../flags/audience.ts"; -import { isFreshCtx } from "../handlers/fresh.ts"; +import { isHandlerContext } from "../handlers/fresh.ts"; import { AppContext } from "../mod.ts"; - export type ConnInfo = Deno.ServeHandlerInfo; export type Handler = Deno.ServeHandler; export interface SelectionConfig { audiences: Routes[]; } - interface MaybePriorityHandler { func: Resolvable<Handler>; highPriority?: boolean; } - const HIGH_PRIORITY_ROUTE_RANK_BASE_VALUE = 1000; - const rankRoute = (pattern: string): number => pattern .split("/") - .reduce( - (acc, routePart) => { - if (routePart === "*") { - return acc; - } - if (routePart.startsWith(":")) { - return acc + 1; - } - if (routePart.includes("*")) { - return acc + 2; - } - return acc + 3; - }, - 0, - ); - + .reduce((acc, routePart) => { + if (routePart === "*") { + return acc; + } + if (routePart.startsWith(":")) { + return acc + 1; + } + if (routePart.includes("*")) { + return acc + 2; + } + return acc + 3; + }, 0); const urlPatternCache: Record<string, URLPattern> = {}; - export const router = ( routes: Route[], hrefRoutes: Record<string, Resolvable<Handler>> = {}, @@ -60,44 +52,31 @@ export const router = ( routePath: string, groups?: Record<string, string | undefined>, ) => { - const ctx = connInfo as - & ConnInfo - & { - params: Record<string, string | undefined>; - state: DecoState; - }; - + const ctx = connInfo as ConnInfo & { + params: Record<string, string | undefined>; + state: DecoState; + }; ctx.params = groups ?? {}; ctx.state.routes = routes; ctx.state.pathTemplate = routePath; - const resolvedOrPromise = isDeferred< Handler, - Omit<RouteContext, "context"> & { context: typeof ctx } + Omit<RouteContext, "context"> & { + context: typeof ctx; + } >(handler) ? handler({ context: ctx, request: req }) - : resolver<Handler>( - handler, - configs, - { context: ctx, request: req }, - ); - + : resolver<Handler>(handler, configs, { context: ctx, request: req }); const hand = isAwaitable(resolvedOrPromise) ? await resolvedOrPromise : resolvedOrPromise; - return await hand(req, ctx); }; - const handler = hrefRoutes[`${url.pathname}${url.search || ""}`] ?? hrefRoutes[url.pathname]; if (handler) { - return route( - handler, - `${url.pathname}${url.search || ""}`, - ); + return route(handler, `${url.pathname}${url.search || ""}`); } - for (const { pathTemplate: routePath, handler } of routes) { const pattern = urlPatternCache[routePath] ??= (() => { let url; @@ -113,47 +92,34 @@ export const router = ( })(); const res = pattern.exec(req.url); const groups = res?.pathname.groups ?? {}; - if (res !== null) { return await route(handler.value, routePath, groups); } } - return new Response(null, { status: 404, }); }; }; - -export const buildRoutes = ( - audiences: Routes[], -): [ +export const buildRoutes = (audiences: Routes[]): [ Record<string, MaybePriorityHandler>, Record<string, Resolvable<Handler>>, ] => { const routeMap: Record<string, MaybePriorityHandler> = {}; const hrefRoutes: Record<string, Resolvable<Handler>> = {}; - // We should tackle this problem elsewhere // check if the audience matches with the given context considering the `isMatch` provided by the cookies. for (const audience of audiences.filter(Boolean).flat()) { - const { - pathTemplate, - isHref, - highPriority, - handler: { value: handler }, - } = audience; - + const { pathTemplate, isHref, highPriority, handler: { value: handler } } = + audience; if (isHref) { hrefRoutes[pathTemplate] = handler; } else { routeMap[pathTemplate] = { func: handler, highPriority }; } } - return [routeMap, hrefRoutes]; }; - export interface SelectionConfig { audiences: Routes[]; } @@ -164,11 +130,9 @@ const RouterId = { ).map((flag) => `${flag.name}@${flag.value}`).join("/"); }, }; - const routerCache = new weakcache.WeakLRUCache({ cacheSize: 16, // up to 16 different routers stored here. }); - const prepareRoutes = (audiences: Routes[], ctx: AppContext) => { const routesFromProps = Array.isArray(audiences) ? audiences : []; // everyone should come first in the list given that we override the everyone value with the upcoming flags. @@ -195,7 +159,6 @@ const prepareRoutes = (audiences: Routes[], ctx: AppContext) => { hrefRoutes, }; }; - /** * @title Router * @description Route requests based on audience @@ -212,12 +175,10 @@ export default function RoutesSelection( status: 404, }); } - const monitoring = isFreshCtx<DecoSiteState>(connInfo) + const monitoring = isHandlerContext<DecoSiteState>(connInfo) ? connInfo.state.monitoring : undefined; - const timing = monitoring?.timings.start("router"); - const routerId = `${RouterId.fromFlags(ctx.flags)}/${ctx.revision ?? ""}`; if (!routerCache.has(routerId)) { routerCache.setValue(routerId, prepareRoutes(audiences, ctx)); @@ -226,16 +187,8 @@ export default function RoutesSelection( routes: Route[]; hrefRoutes: Record<string, Resolvable<Handler>>; } = routerCache.getValue(routerId); - const server = router( - routes, - hrefRoutes, - ctx.get, - { monitoring }, - url, - ); - + const server = router(routes, hrefRoutes, ctx.get, { monitoring }, url); timing?.end(); - return await server(req, connInfo); }; } diff --git a/website/handlers/sitemap.ts b/website/handlers/sitemap.ts index a03dd0d3d..493c2e933 100644 --- a/website/handlers/sitemap.ts +++ b/website/handlers/sitemap.ts @@ -1,15 +1,11 @@ -import type { Handler } from "deco/blocks/handler.ts"; -import type { Resolvable } from "deco/engine/core/resolver.ts"; -import { isResolvable } from "deco/engine/core/resolver.ts"; import { Route } from "../flags/audience.ts"; - +import { type Handler } from "@deco/deco/blocks"; +import { isResolvable, type Resolvable } from "@deco/deco"; const isPage = (handler: Resolvable<Handler>) => isResolvable(handler) && handler.__resolveType.endsWith("handlers/fresh.ts"); - const isAbsolute = (href: string) => !href.includes(":") && !href.includes("*") && !href.startsWith("/_live"); - const buildSiteMap = (urls: string[]) => { const entries: string[] = []; for (const url of urls) { @@ -22,7 +18,6 @@ const buildSiteMap = (urls: string[]) => { } return entries.join("\n"); }; - const sanitize = (url: string) => url.startsWith("/") ? url : `/${url}`; const siteMapFromRoutes = ( publicUrl: string, @@ -41,11 +36,9 @@ const siteMapFromRoutes = ( } return buildSiteMap(urls); }; - interface Props { excludePaths?: string[]; } - /** * @title Sitemap * @description Return deco's sitemap.xml diff --git a/website/loaders/asset.ts b/website/loaders/asset.ts index 5a59d7bab..0d931b173 100644 --- a/website/loaders/asset.ts +++ b/website/loaders/asset.ts @@ -1,29 +1,22 @@ -import { shortcircuit } from "deco/engine/errors.ts"; import { fetchSafe, STALE } from "../../utils/fetch.ts"; - +import { shortcircuit } from "@deco/deco"; interface Props { /** * @description Asset src like: https://fonts.gstatic.com/... */ src: string; } - const loader = async (props: Props) => { const url = new URL(props.src); - if (url.protocol === "file:") { shortcircuit(new Response("Forbidden", { status: 403 })); } - const original = await fetchSafe(url.href, STALE); - const response = new Response(original.body, original); response.headers.set( "cache-control", "public, s-maxage=15552000, max-age=15552000, immutable", ); - return response; }; - export default loader; diff --git a/website/loaders/extension.ts b/website/loaders/extension.ts index 1cdd803f4..298fbb99d 100644 --- a/website/loaders/extension.ts +++ b/website/loaders/extension.ts @@ -1,10 +1,8 @@ -import { PromiseOrValue } from "deco/engine/core/utils.ts"; - +import { PromiseOrValue } from "@deco/deco/utils"; /** * @title The type extension. */ export type ExtensionOf<T> = (value: T) => PromiseOrValue<T>; - export interface Props<T> { /** * @title Data @@ -16,12 +14,10 @@ export interface Props<T> { */ extensions: ExtensionOf<T>[]; } - // Merge user props with invoke props export const onBeforeResolveProps = <T>( { data, extensions, ...props }: Props<T>, ) => ({ data: { ...data, ...props }, extensions }); - export default async function Extended<T>( { data, extensions }: Props<T>, ): Promise<T> { diff --git a/website/loaders/options/routes.ts b/website/loaders/options/routes.ts index 52620d995..a560ddc77 100644 --- a/website/loaders/options/routes.ts +++ b/website/loaders/options/routes.ts @@ -1,14 +1,11 @@ -import { allowCorsFor } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; - -type Resolvable< - T extends Record<string, unknown> = Record<string, unknown>, -> = { +import { allowCorsFor } from "@deco/deco"; +type Resolvable<T extends Record<string, unknown> = Record<string, unknown>> = { __resolveType: string; } & T; - -type HandlerValue = Resolvable<{ page: Resolvable }>; - +type HandlerValue = Resolvable<{ + page: Resolvable; +}>; export default async function loader( _props: unknown, req: Request, @@ -17,9 +14,7 @@ export default async function loader( Object.entries(allowCorsFor(req)).map(([name, value]) => { ctx.response.headers.set(name, value); }); - const pages = await ctx.invoke.website.loaders.pages(); - return pages?.map((route) => ({ label: `${ (route.handler.value as HandlerValue).page.__resolveType.split("-") diff --git a/website/loaders/options/urlParams.ts b/website/loaders/options/urlParams.ts index c607c2987..593524e2f 100644 --- a/website/loaders/options/urlParams.ts +++ b/website/loaders/options/urlParams.ts @@ -1,24 +1,18 @@ -import { allowCorsFor } from "deco/mod.ts"; import { AppContext } from "../../mod.ts"; - +import { allowCorsFor } from "@deco/deco"; export interface Props { path: string; } - const loader = (props: Props, req: Request, ctx: AppContext): string[] => { Object.entries(allowCorsFor(req)).map(([name, value]) => { ctx.response.headers.set(name, value); }); - const params: string[] = []; - props.path.split("/").forEach((param) => { if (param.startsWith(":")) { params.push(param.substring(1)); } }); - return params; }; - export default loader; diff --git a/website/loaders/pages.ts b/website/loaders/pages.ts index 44540cd8d..23465c2ef 100644 --- a/website/loaders/pages.ts +++ b/website/loaders/pages.ts @@ -1,4 +1,3 @@ -import defaults from "deco/engine/manifest/defaults.ts"; import { Route } from "../flags/audience.ts"; import { AppContext } from "../mod.ts"; import Page from "../pages/Page.tsx"; @@ -8,7 +7,7 @@ async function getAllPages(ctx: AppContext): Promise<Route[]> { Record<string, Parameters<typeof Page>[0]> >({ type: "pages", - __resolveType: defaults["blockSelector"].name, + __resolveType: "blockSelector", }); const routes: Route[] = []; @@ -46,7 +45,7 @@ export default async function Pages( Route[] >({ func: () => getAllPages(ctx), - __resolveType: defaults["once"].name, + __resolveType: "once", }); return allPages; diff --git a/website/loaders/redirects.ts b/website/loaders/redirects.ts index c18e16f97..7ba47c2ff 100644 --- a/website/loaders/redirects.ts +++ b/website/loaders/redirects.ts @@ -1,4 +1,3 @@ -import defaults from "deco/engine/manifest/defaults.ts"; import { Route } from "../flags/audience.ts"; import { AppContext } from "../mod.ts"; import { type Props as RedirectProps } from "./redirect.ts"; @@ -9,7 +8,7 @@ const isHref = (from: string) => async function getAllRedirects(ctx: AppContext): Promise<Route[]> { const allRedirects = await ctx.get<RedirectProps[]>({ resolveType: "website/loaders/redirect.ts", - __resolveType: defaults["resolveTypeSelector"].name, + __resolveType: "resolveTypeSelector", }); const routes: Route[] = allRedirects.map(({ redirect }) => ({ @@ -38,7 +37,7 @@ export default async function Redirects( const allRedirects = await ctx.get<Route[]>({ key: "getAllRedirects", func: () => getAllRedirects(ctx), - __resolveType: defaults["once"].name, + __resolveType: "once", }); return allRedirects; diff --git a/website/loaders/secret.ts b/website/loaders/secret.ts index c0bceed1a..7836aee96 100644 --- a/website/loaders/secret.ts +++ b/website/loaders/secret.ts @@ -1,7 +1,7 @@ -import { Context } from "deco/deco.ts"; import * as colors from "std/fmt/colors.ts"; import { once } from "../../typesense/utils/once.ts"; import { decryptFromHex, hasLocalCryptoKey } from "../utils/crypto.ts"; +import { Context } from "@deco/deco"; /** * @title Secret * @hideOption true @@ -12,7 +12,6 @@ export interface Secret { */ get: () => string | null; } - export interface Props { /** * @title Secret Value @@ -26,9 +25,7 @@ export interface Props { */ name?: string; } - const cache: Record<string, Promise<string | null>> = {}; - const showWarningOnce = once(() => { console.warn( colors.brightYellow( @@ -62,9 +59,7 @@ const getSecret = async (props: Props): Promise<string | null> => { /** * @title Secret */ -export default async function Secret( - props: Props, -): Promise<Secret> { +export default async function Secret(props: Props): Promise<Secret> { const secretValue = await getSecret(props); return { get: (): string | null => { diff --git a/website/matchers/cookie.ts b/website/matchers/cookie.ts index f41a44db8..557972e49 100644 --- a/website/matchers/cookie.ts +++ b/website/matchers/cookie.ts @@ -1,6 +1,5 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; import { getCookies } from "std/http/cookie.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; /** * @title Cookie */ @@ -8,18 +7,13 @@ export interface Props { name: string; value: string; } - /** * @title Cookie * @description Target users that have a specific cookie * @icon cookie */ -const MatchCookie = ( - { name, value }: Props, - { request }: MatchContext, -) => { +const MatchCookie = ({ name, value }: Props, { request }: MatchContext) => { const cookies = getCookies(request.headers); return cookies[name] === value; }; - export default MatchCookie; diff --git a/website/matchers/device.ts b/website/matchers/device.ts index b2d5ab0a2..c34226619 100644 --- a/website/matchers/device.ts +++ b/website/matchers/device.ts @@ -1,10 +1,8 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; /** * @title {{{.}}} */ export type Device = "mobile" | "tablet" | "desktop"; - /** * @title {{#mobile}}Mobile{{/mobile}} {{#tablet}}Tablet{{/tablet}} {{#desktop}}Desktop{{/desktop}} */ @@ -22,12 +20,10 @@ export interface Props { */ desktop?: boolean; } - // backwards compatibility interface OldProps { devices: Device[]; } - /** * @title Device * @description Target users based on their device type, such as desktop, tablet, or mobile @@ -41,8 +37,6 @@ const MatchDevice = ( mobile && devices.push("mobile"); tablet && devices.push("tablet"); desktop && devices.push("desktop"); - return devices.includes(device); }; - export default MatchDevice; diff --git a/website/matchers/host.ts b/website/matchers/host.ts index 7b89f73fd..2417d61a0 100644 --- a/website/matchers/host.ts +++ b/website/matchers/host.ts @@ -1,5 +1,4 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; /** * @title {{{includes}}} {{{match}}} */ @@ -7,22 +6,16 @@ export interface Props { includes?: string; match?: string; } - /** * @title Host * @description Target users based on the domain or subdomain they are accessing your site from * @icon world-www */ -const MatchHost = ( - { includes, match }: Props, - { request }: MatchContext, -) => { +const MatchHost = ({ includes, match }: Props, { request }: MatchContext) => { const host = request.headers.get("host") || request.headers.get("origin") || ""; const regexMatch = match ? new RegExp(match).test(host) : true; const includesFound = includes ? host.includes(includes) : true; - return regexMatch && includesFound; }; - export default MatchHost; diff --git a/website/matchers/location.ts b/website/matchers/location.ts index 66e9f25d7..7f5bac86b 100644 --- a/website/matchers/location.ts +++ b/website/matchers/location.ts @@ -1,13 +1,11 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; import { MapWidget } from "../../admin/widgets.ts"; import { haversine } from "../utils/location.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; export interface Coordinate { latitude: number; longitude: number; radius?: number; } - export interface Map { /** * @title Area selection @@ -15,7 +13,6 @@ export interface Map { */ coordinates?: MapWidget; } - export interface Location { /** * @title City @@ -33,7 +30,6 @@ export interface Location { */ country?: string; } - export interface Props { /** * @title Include Locations @@ -44,7 +40,6 @@ export interface Props { */ excludeLocations?: (Location | Map)[]; } - export interface MapLocation { /** * @title City @@ -61,14 +56,12 @@ export interface MapLocation { * @example BR */ country?: string; - /** * @title Area selection * @example -7.27820,-35.97630,2000 */ coordinates?: MapWidget; } - const matchLocation = (defaultNotMatched = true, source: MapLocation) => (target: MapLocation) => { if ( @@ -79,24 +72,18 @@ const matchLocation = ) { return defaultNotMatched; } - let result = !target.regionCode || target.regionCode === source.regionCode; result &&= !source.coordinates || !target.coordinates || haversine(source.coordinates, target.coordinates) <= Number(target.coordinates.split(",")[2]); - result &&= !target.city || target.city === source.city; result &&= !target.country || target.country === source.country; return result; }; - -const escaped = ({ - city, - country, - regionCode, - coordinates, -}: MapLocation): MapLocation => { +const escaped = ( + { city, country, regionCode, coordinates }: MapLocation, +): MapLocation => { return { coordinates, regionCode, @@ -126,12 +113,9 @@ export default function MatchLocation( if (isLocationExcluded) { return false; } - if (includeLocations?.length === 0) { return true; } - - return ( - includeLocations?.some(matchLocation(true, escaped(userLocation))) ?? true - ); + return (includeLocations?.some(matchLocation(true, escaped(userLocation))) ?? + true); } diff --git a/website/matchers/multi.ts b/website/matchers/multi.ts index 664aa0b82..a5dc642a4 100644 --- a/website/matchers/multi.ts +++ b/website/matchers/multi.ts @@ -1,5 +1,4 @@ -import { MatchContext, Matcher } from "deco/blocks/matcher.ts"; - +import { type MatchContext, type Matcher } from "@deco/deco/blocks"; /** * @title Combined options with {{{op}}} */ @@ -7,7 +6,6 @@ export interface Props { op: "or" | "and"; matchers: Matcher[]; } - /** * @title Multi * @description Create more complex conditions by combining multiple matchers @@ -18,5 +16,4 @@ const MatchMulti = ({ op, matchers }: Props) => (ctx: MatchContext) => { ? matchers.some((matcher) => matcher(ctx)) : matchers.every((matcher) => matcher(ctx)); }; - export default MatchMulti; diff --git a/website/matchers/negate.ts b/website/matchers/negate.ts index 60f130185..0b1231b3d 100644 --- a/website/matchers/negate.ts +++ b/website/matchers/negate.ts @@ -1,12 +1,10 @@ -import { MatchContext, Matcher } from "deco/blocks/matcher.ts"; - +import { type MatchContext, type Matcher } from "@deco/deco/blocks"; export interface Props { /** * @description Matcher to be negated. */ matcher: Matcher; } - /** * @title Negates * @description Create conditions that target users who do not meet certain criteria @@ -15,5 +13,4 @@ export interface Props { const NegateMacher = ({ matcher }: Props) => (ctx: MatchContext) => { return !matcher(ctx); }; - export default NegateMacher; diff --git a/website/matchers/pathname.ts b/website/matchers/pathname.ts index e2f9269f0..3594b3ddc 100644 --- a/website/matchers/pathname.ts +++ b/website/matchers/pathname.ts @@ -1,5 +1,4 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; interface BaseCase { /** * @title Pathname @@ -7,7 +6,6 @@ interface BaseCase { */ pathname?: string; } - /** * @title Equals */ @@ -18,7 +16,6 @@ interface Equals extends BaseCase { */ type: "Equals"; } - interface Includes extends BaseCase { /** * @readonly @@ -26,16 +23,12 @@ interface Includes extends BaseCase { */ type: "Includes"; } - export interface Props { /** * @title Operation */ - case: - | Equals - | Includes; + case: Equals | Includes; } - const operations: Record< Props["case"]["type"], (pathname: string, condition: string) => boolean @@ -43,22 +36,17 @@ const operations: Record< Equals: (pathname, value) => pathname === value, Includes: (pathname, value) => pathname.includes(value), }); - /** * @title Pathname * @description Target users based on the pathname * @icon world-www */ -const MatchPathname = ( - props: Props, - { request }: MatchContext, -) => { +const MatchPathname = (props: Props, { request }: MatchContext) => { const url = new URL(request.url); const pathname = url.pathname; - - if (!props.case.pathname) return false; - + if (!props.case.pathname) { + return false; + } return operations[props.case.type](pathname, props.case.pathname); }; - export default MatchPathname; diff --git a/website/matchers/queryString.ts b/website/matchers/queryString.ts index 3331ca013..3ef9de0e4 100644 --- a/website/matchers/queryString.ts +++ b/website/matchers/queryString.ts @@ -1,9 +1,7 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; interface BaseCase { value: string; } - /** * @title Equals */ @@ -14,7 +12,6 @@ interface Equals extends BaseCase { */ type: "Equals"; } - interface Greater extends BaseCase { /** * @readonly @@ -22,7 +19,6 @@ interface Greater extends BaseCase { */ type: "Greater"; } - interface Lesser extends BaseCase { /** * @readonly @@ -30,7 +26,6 @@ interface Lesser extends BaseCase { */ type: "Lesser"; } - interface GreaterOrEquals extends BaseCase { /** * @readonly @@ -38,7 +33,6 @@ interface GreaterOrEquals extends BaseCase { */ type: "GreaterOrEquals"; } - interface LesserOrEquals extends BaseCase { /** * @readonly @@ -46,7 +40,6 @@ interface LesserOrEquals extends BaseCase { */ type: "LesserOrEquals"; } - interface Includes extends BaseCase { /** * @readonly @@ -54,7 +47,6 @@ interface Includes extends BaseCase { */ type: "Includes"; } - interface Exists { /** * @readonly @@ -62,7 +54,6 @@ interface Exists { */ type: "Exists"; } - /* * @title {{{param}}} {{{case.type}}} {{{case.value}}} */ @@ -77,26 +68,23 @@ interface Condition { | Includes | Exists; } - /** * @title Query String Matcher */ export interface Props { conditions: Condition[]; } - const matchesAtLeastOne = ( params: string[], condition: Condition, compare: (a: string, b: string) => boolean, ) => { - if (condition.case.type === "Exists") return false; - + if (condition.case.type === "Exists") { + return false; + } const value = condition.case.value as string; - return params.filter((param) => compare(param, value)).length > 0; }; - const operations: Record< Condition["case"]["type"], (param: string[], condition: Condition) => boolean @@ -115,30 +103,22 @@ const operations: Record< matchesAtLeastOne(params, condition, (a, b) => a <= b), Exists: (_params, _condition) => true, }); - /** * @title Query String * @description Match with a specific querystring * @icon question-mark */ -const MatchQueryString = ( - props: Props, - { request }: MatchContext, -) => { +const MatchQueryString = (props: Props, { request }: MatchContext) => { let matches = true; const url = new URL(request.url); - props.conditions.forEach((condition) => { const params = url.searchParams.getAll(condition.param); if (!params.length) { matches = false; return; } - matches = matches && operations[condition.case.type](params, condition); }); - return matches; }; - export default MatchQueryString; diff --git a/website/matchers/site.ts b/website/matchers/site.ts index 7cace5f99..1318f2472 100644 --- a/website/matchers/site.ts +++ b/website/matchers/site.ts @@ -1,12 +1,10 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; /** * @title {{{siteId}}} */ export interface Props { siteId: number; } - /** * @title Site * @description Target users based on the deco website ID they are on @@ -15,5 +13,4 @@ export interface Props { const MatchSite = ({ siteId }: Props, { siteId: currSiteId }: MatchContext) => { return siteId === currSiteId; }; - export default MatchSite; diff --git a/website/matchers/userAgent.ts b/website/matchers/userAgent.ts index fdbece6c4..7d279a88e 100644 --- a/website/matchers/userAgent.ts +++ b/website/matchers/userAgent.ts @@ -1,5 +1,4 @@ -import { MatchContext } from "deco/blocks/matcher.ts"; - +import { type MatchContext } from "@deco/deco/blocks"; /** * @title {{{includes}}} {{{match}}} */ @@ -7,7 +6,6 @@ export interface Props { includes?: string; match?: string; } - /** * @title User Agent * @description Target users based on their web browser or operational system @@ -20,8 +18,6 @@ const MatchUserAgent = ( const ua = request.headers.get("user-agent") || ""; const regexMatch = match ? new RegExp(match).test(ua) : true; const includesFound = includes ? ua.includes(includes) : true; - return regexMatch && includesFound; }; - export default MatchUserAgent; diff --git a/website/mod.ts b/website/mod.ts index 8c7492d5d..0460a1c1b 100644 --- a/website/mod.ts +++ b/website/mod.ts @@ -1,53 +1,51 @@ import "./utils/unhandledRejection.ts"; - -import { Matcher } from "deco/blocks/matcher.ts"; -import { Page } from "deco/blocks/page.tsx"; -import { Section } from "deco/blocks/section.ts"; -import type { App, Flag, FnContext, Site } from "deco/mod.ts"; -import { asResolved } from "deco/mod.ts"; import type { Props as Seo } from "./components/Seo.tsx"; import { Routes } from "./flags/audience.ts"; import { TextReplace } from "./handlers/proxy.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { Script } from "./types.ts"; - +import { type Matcher, type Page, type Section } from "@deco/deco/blocks"; +import { + type App, + asResolved, + type Flag, + type FnContext, + type Site, +} from "@deco/deco"; declare global { interface Window { LIVE: { - page: { id: string | number; pathTemplate?: string | undefined }; + page: { + id: string | number; + pathTemplate?: string | undefined; + }; site: Site; flags?: Flag[]; play?: boolean; }; } } - export type AppContext = FnContext<Props, Manifest>; - -export type SectionProps<T> = T & { id: string }; - +export type SectionProps<T> = T & { + id: string; +}; export interface CacheDirectiveBase { name: string; value: number; } - export interface StaleWhileRevalidate extends CacheDirectiveBase { name: "stale-while-revalidate"; value: number; } - export interface MaxAge extends CacheDirectiveBase { name: "max-age"; value: number; } - export type CacheDirective = StaleWhileRevalidate | MaxAge; - export interface Caching { enabled?: boolean; directives?: CacheDirective[]; } - export interface AbTesting { enabled?: boolean; /** @@ -71,43 +69,36 @@ export interface AbTesting { includes?: Script[]; }; } - /** @titleBy framework */ interface Fresh { /** @default fresh */ framework: "fresh"; } - /** @titleBy framework */ interface HTMX { /** @default htmx */ framework: "htmx"; } - export interface Props { /** * @title Routes Map */ routes?: Routes[]; - /** * @title Global Sections * @description These sections will be included on the start of each page */ global?: Section[]; - /** * @title Error Page * @description This page will be used when something goes wrong beyond section error-boundaries when rendering a page */ errorPage?: Page; - /** * @title Caching configuration of pages * @description the caching configuration */ caching?: Caching; - /** * @title Global Async Rendering (Deprecated) * @description Please disable this setting and enable each section individually. More info at https://deco.cx/en/blog/async-render-default @@ -115,36 +106,27 @@ export interface Props { * @default false */ firstByteThresholdMS?: boolean; - /** * @title Avoid redirecting to editor * @description Disable going to editor when "." or "Ctrl + Shift + E" is pressed */ avoidRedirectingToEditor?: boolean; - /** * @title AB Testing * @description A/B Testing configuration */ abTesting?: AbTesting; - /** * @title Flavor * @description The flavor of the website */ flavor?: Fresh | HTMX; - /** @title Seo */ - seo?: Omit< - Seo, - "jsonLDs" | "canonical" - >; - + seo?: Omit<Seo, "jsonLDs" | "canonical">; /** * @title Theme */ theme?: Section; - // We are hiding this prop because it is in testing phase // after that, probably we will remove this prop and default will be true /** @@ -152,7 +134,6 @@ export interface Props { */ sendToClickHouse?: boolean; } - /** * @title Website */ @@ -188,7 +169,6 @@ export default function App({ ...state }: Props): App<Manifest, Props> { }, }; } - const getAbTestAudience = (abTesting: AbTesting) => { const handler = { value: { @@ -199,7 +179,6 @@ const getAbTestAudience = (abTesting: AbTesting) => { replaces: abTesting.replaces, }, }; - if (abTesting.enabled) { return [{ name: abTesting.name, @@ -216,7 +195,6 @@ const getAbTestAudience = (abTesting: AbTesting) => { } return []; }; - const deferPropsResolve = (routes: Routes): Routes => { if (Array.isArray(routes)) { const newRoutes = []; @@ -232,7 +210,6 @@ const deferPropsResolve = (routes: Routes): Routes => { } return routes; }; - export const onBeforeResolveProps = < T extends { routes?: Routes[]; @@ -241,9 +218,7 @@ export const onBeforeResolveProps = < global: Section[]; theme: Section; }, ->( - props: T, -): T => { +>(props: T): T => { if (Array.isArray(props?.routes)) { const newRoutes: T = { ...props, @@ -260,5 +235,4 @@ export const onBeforeResolveProps = < } return props; }; - export { default as Preview } from "./Preview.tsx"; diff --git a/website/pages/Page.tsx b/website/pages/Page.tsx index e57c42c46..85c87190d 100644 --- a/website/pages/Page.tsx +++ b/website/pages/Page.tsx @@ -1,15 +1,19 @@ import { Head } from "$fresh/runtime.ts"; -import type { Page } from "deco/blocks/page.tsx"; -import { Section, SectionProps } from "deco/blocks/section.ts"; -import { ComponentFunc, ComponentMetadata } from "deco/engine/block.ts"; -import { HttpError } from "deco/engine/errors.ts"; -import { Context } from "deco/live.ts"; import { + Context, + HttpError, isDeferred, + type SectionProps, usePageContext as useDecoPageContext, useRouterContext, -} from "deco/mod.ts"; -import { logger } from "deco/observability/otel/config.ts"; +} from "@deco/deco"; +import { + type ComponentFunc, + type ComponentMetadata, + type Page, + type Section, +} from "@deco/deco/blocks"; +import { logger } from "@deco/deco/o11y"; import { Component, JSX } from "preact"; import ErrorPageComponent from "../../utils/defaultErrorPage.tsx"; import Clickhouse, { @@ -20,20 +24,16 @@ import Events from "../components/Events.tsx"; import { SEOSection } from "../components/Seo.tsx"; import LiveControls from "../components/_Controls.tsx"; import { AppContext } from "../mod.ts"; - const noIndexedDomains = ["decocdn.com", "deco.site", "deno.dev"]; - /** * @title Sections * @label hidden * @changeable true */ export type Sections = Section[]; - export interface DefaultPathProps { possiblePaths: string[]; } - /** * @titleBy name * @label rootHidden @@ -50,23 +50,22 @@ export interface Props { /** @hide true */ unindexedDomain?: boolean; } - export function renderSection(section: Props["sections"][number]) { - if (section === undefined || section === null) return <></>; + if (section === undefined || section === null) { + return <></>; + } const { Component, props } = section; - return <Component {...props} />; } - -class ErrorBoundary // deno-lint-ignore no-explicit-any - extends Component<{ fallback: ComponentFunc<any> }> { +class ErrorBoundary extends Component<{ + // deno-lint-ignore no-explicit-any + fallback: ComponentFunc<any>; +}> { state = { error: null as Error | null }; - // deno-lint-ignore no-explicit-any static getDerivedStateFromError(error: any) { return { error }; } - render() { if (this.state.error) { const err = this?.state?.error; @@ -81,7 +80,6 @@ class ErrorBoundary // deno-lint-ignore no-explicit-any : this.props.fallback(this.state.error); } } - const useDeco = () => { const metadata = useDecoPageContext()?.metadata; const routerCtx = useRouterContext(); @@ -94,25 +92,25 @@ const useDeco = () => { }, }; }; - /** * @title Page */ -function Page({ - sections, - errorPage, - devMode, - seo, - unindexedDomain, - avoidRedirectingToEditor, - sendToClickHouse, - userId, - sessionId, -}: SectionProps<typeof loader>): JSX.Element { +function Page( + { + sections, + errorPage, + devMode, + seo, + unindexedDomain, + avoidRedirectingToEditor, + sendToClickHouse, + userId, + sessionId, + }: SectionProps<typeof loader>, +): JSX.Element { const context = Context.active(); const site = { id: context.siteId, name: context.site }; const deco = useDeco(); - return ( <> {unindexedDomain && ( @@ -155,17 +153,14 @@ function Page({ </> ); } - const getClientIp = (req: Request): string => { return req.headers.get("CF-Connecting-IP") || req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || ""; }; - const getUserAgent = (req: Request): string => { return req.headers.get("user-agent") || ""; }; - export const loader = async ( { sections, ...restProps }: Props, req: Request, @@ -173,33 +168,26 @@ export const loader = async ( ) => { const url = new URL(req.url); const devMode = url.searchParams.has("__d"); - const unindexedDomain = noIndexedDomains.some((domain) => url.origin.includes(domain) ); - const global = ctx.global || []; - const resolvedGlobals = await Promise.all( global?.map(async (section) => { return await ctx.get(section); }), ); - const globalSections = ctx.theme ? [ctx.theme, ...resolvedGlobals] : resolvedGlobals; - const context = Context.active(); const site = { id: context.siteId, name: context.site }; - const userId = await generateUserId( site.name, getClientIp(req), getUserAgent(req), ); const sessionId = generateSessionId(); - return { ...restProps, sections: [...globalSections, ...sections], @@ -214,11 +202,9 @@ export const loader = async ( sessionId, }; }; - export function Preview(props: SectionProps<typeof loader>) { const { sections, seo } = props; const deco = useDeco(); - return ( <> <Head> @@ -231,23 +217,19 @@ export function Preview(props: SectionProps<typeof loader>) { </> ); } - const PAGE_NOT_FOUND = -1; export const pageIdFromMetadata = (metadata: ComponentMetadata | undefined) => { if (!metadata) { return PAGE_NOT_FOUND; } - const { resolveChain, component } = metadata; - const pageResolverIndex = resolveChain.findLastIndex( - (chain) => chain.type === "resolver" && chain.value === component, - ) || PAGE_NOT_FOUND; - + const pageResolverIndex = + resolveChain.findLastIndex((chain) => + chain.type === "resolver" && chain.value === component + ) || PAGE_NOT_FOUND; const pageParent = pageResolverIndex > 0 ? resolveChain[pageResolverIndex - 1] : null; - return pageParent?.value ?? PAGE_NOT_FOUND; }; - export default Page; diff --git a/website/sections/Rendering/Deferred.tsx b/website/sections/Rendering/Deferred.tsx index fb55b2d9f..b7bb42ae5 100644 --- a/website/sections/Rendering/Deferred.tsx +++ b/website/sections/Rendering/Deferred.tsx @@ -1,15 +1,12 @@ -import type { Section } from "deco/blocks/section.ts"; -import { usePartialSection } from "deco/hooks/usePartialSection.ts"; -import { useScriptAsDataURI } from "deco/hooks/useScript.ts"; -import { asResolved, isDeferred } from "deco/mod.ts"; import { useId } from "preact/hooks"; import { AppContext } from "../../mod.ts"; import { shouldForceRender } from "../../../utils/deferred.ts"; - +import { type Section } from "@deco/deco/blocks"; +import { usePartialSection, useScriptAsDataURI } from "@deco/deco/hooks"; +import { asResolved, isDeferred } from "@deco/deco"; /** @titleBy type */ export interface Scroll { type: "scroll"; - /** * @hide true * @title Delay MS @@ -17,10 +14,8 @@ export interface Scroll { */ payload: number; } - interface Load { type: "load"; - /** * @hide true * @title Delay MS @@ -28,7 +23,6 @@ interface Load { */ payload: number; } - /** @titleBy type */ export interface Intersection { type: "intersection"; @@ -38,46 +32,37 @@ export interface Intersection { */ payload: string; } - export interface Props { sections: Section[]; display?: boolean; behavior?: Scroll | Intersection | Load; } - const script = ( id: string, type: "scroll" | "intersection" | "load", payload: string, ) => { const element = document.getElementById(id); - if (!element) { return; } - const triggerRender = (timeout: number) => () => { setTimeout(() => element.click(), timeout); }; - if (type === "load") { const timeout = Number(payload || 200); const instant = timeout === 0; - - if (instant || document.readyState === "complete") triggerRender(timeout); - else { + if (instant || document.readyState === "complete") { + triggerRender(timeout); + } else { addEventListener("DOMContentLoaded", triggerRender(timeout)); } } - if (type === "scroll") { - addEventListener( - "scroll", - triggerRender(Number(payload) ?? 200), - { once: true }, - ); + addEventListener("scroll", triggerRender(Number(payload) ?? 200), { + once: true, + }); } - if (type === "intersection") { new IntersectionObserver((entries) => { for (const entry of entries) { @@ -89,7 +74,6 @@ const script = ( }, { rootMargin: payload || "200px" }).observe(element); } }; - const Deferred = (props: Props) => { const { sections, display, behavior } = props; const sectionID = useId(); @@ -97,7 +81,6 @@ const Deferred = (props: Props) => { const partial = usePartialSection<typeof Deferred>({ props: { display: true }, }); - if (display) { return ( <> @@ -105,7 +88,6 @@ const Deferred = (props: Props) => { </> ); } - return ( <> <button @@ -126,12 +108,10 @@ const Deferred = (props: Props) => { </> ); }; - export const loader = async (props: Props, req: Request, ctx: AppContext) => { const url = new URL(req.url); const shouldRender = props.display === true || shouldForceRender({ ctx, searchParams: url.searchParams }); - if (shouldRender) { const sections = isDeferred(props.sections) ? await props.sections() @@ -142,12 +122,9 @@ export const loader = async (props: Props, req: Request, ctx: AppContext) => { sections, }; } - return { ...props, sections: [] }; }; - const DEFERRED = true; - export const onBeforeResolveProps = (props: Props) => { return { ...props, @@ -155,5 +132,4 @@ export const onBeforeResolveProps = (props: Props) => { sections: asResolved(props.sections, DEFERRED), }; }; - export default Deferred; diff --git a/website/sections/Rendering/Lazy.tsx b/website/sections/Rendering/Lazy.tsx index ba1bbea37..ef8fa063d 100644 --- a/website/sections/Rendering/Lazy.tsx +++ b/website/sections/Rendering/Lazy.tsx @@ -1,28 +1,23 @@ -import type { Section } from "deco/blocks/section.ts"; +import { asResolved, context, isDeferred, RequestContext } from "@deco/deco"; import { + type Section, type SectionContext, - SectionContext as SectionCtx, -} from "deco/components/section.tsx"; -import { RequestContext } from "deco/deco.ts"; -import { asResolved, context, isDeferred } from "deco/mod.ts"; + SectionCtx, +} from "@deco/deco/blocks"; import { useContext } from "preact/hooks"; import { shouldForceRender } from "../../../utils/deferred.ts"; import type { AppContext } from "../../mod.ts"; - const useSectionContext = () => useContext<SectionContext | undefined>(SectionCtx); - interface Props { /** @label hidden */ section: Section; - /** * @description htmx/Deferred.tsx prop * @hide true */ loading?: "eager" | "lazy"; } - const defaultFallbackFor = (section: string) => () => ( <div style={{ @@ -67,51 +62,36 @@ const defaultFallbackFor = (section: string) => () => ( )} </div> ); - export const loader = async (props: Props, req: Request, ctx: AppContext) => { const { section, loading } = props; const url = new URL(req.url); - const shouldRender = loading === "eager" || shouldForceRender({ ctx, searchParams: url.searchParams, }); - if (shouldRender) { return { loading: "eager", section: isDeferred<Section>(section) ? await section() : section, }; } - const abortController = new AbortController(); abortController.abort(); const resolveSection = isDeferred<Section>(section) - ? RequestContext.bind( - { signal: abortController.signal }, - section, - ) + ? RequestContext.bind({ signal: abortController.signal }, section) : () => section; const resolvedSection = await resolveSection({}, { propagateOptions: true, hooks: { - onPropsResolveStart: ( - resolve, - _props, - resolver, - ) => { + onPropsResolveStart: (resolve, _props, resolver) => { let next = resolve; if (resolver?.type === "matchers") { // matchers should not have a timeout. - next = RequestContext.bind( - { signal: req.signal }, - resolve, - ); + next = RequestContext.bind({ signal: req.signal }, resolve); } return next(); }, }, }); - return { loading: shouldRender ? "eager" : "lazy", section: { @@ -122,16 +102,12 @@ export const loader = async (props: Props, req: Request, ctx: AppContext) => { }, }; }; - type SectionProps = Awaited<ReturnType<typeof loader>>; - function Lazy({ section, loading }: SectionProps) { const ctx = useSectionContext(); - if (!ctx) { throw new Error("Missing SectionContext"); } - if (loading === "lazy") { return ( <ctx.FallbackWrapper props={{ loading: "eager" }}> @@ -139,13 +115,10 @@ function Lazy({ section, loading }: SectionProps) { </ctx.FallbackWrapper> ); } - return <section.Component {...section.props} />; } - export const onBeforeResolveProps = (props: Props) => ({ ...props, section: asResolved(props.section, true), }); - export default Lazy; diff --git a/website/utils/multivariate.ts b/website/utils/multivariate.ts index 9f083de98..7b58de292 100644 --- a/website/utils/multivariate.ts +++ b/website/utils/multivariate.ts @@ -1,8 +1,5 @@ -import { Variant } from "deco/blocks/flag.ts"; -import type { MultivariateFlag } from "deco/blocks/flag.ts"; -import { asResolved } from "deco/engine/core/resolver.ts"; -import { HintNode } from "deco/engine/core/hints.ts"; - +import { type MultivariateFlag, type Variant } from "@deco/deco/blocks"; +import { asResolved, type HintNode } from "@deco/deco"; /** * @title Multivariate */ @@ -13,7 +10,6 @@ export interface MultivariateProps<T> { */ variants: Variant<T>[]; } - /** * @title Variant * @label hidden @@ -23,21 +19,18 @@ export default function MultivariateFlag<T>( ): MultivariateFlag<T> { return props; } - const isMultivariateProps = ( props: unknown | MultivariateProps<unknown>, ): props is MultivariateProps<unknown> => { return (props as MultivariateProps<unknown>)?.variants !== undefined && Array.isArray((props as MultivariateProps<unknown>)?.variants); }; - const isVariant = ( variant: unknown | Variant<unknown>, ): variant is Variant<unknown> => { return (variant as Variant<unknown>).value !== undefined && typeof variant === "object"; }; - /** * This is used to avoid resolving flag values before matcher is actually evaluated */ @@ -47,7 +40,6 @@ export const onBeforeResolveProps = ( ) => { if (isMultivariateProps(props)) { const newVariants = []; - for (let idx = 0; idx < props.variants.length; idx++) { const variant = props.variants[idx]; if (isVariant(variant)) { @@ -62,7 +54,6 @@ export const onBeforeResolveProps = ( newVariants.push(variant); } } - if (newVariants.length > 0) { // avoid shallow copy return { ...props, diff --git a/workflows/actions/start.ts b/workflows/actions/start.ts index 24c91e8e2..e5ea323d6 100644 --- a/workflows/actions/start.ts +++ b/workflows/actions/start.ts @@ -1,13 +1,16 @@ // deno-lint-ignore-file no-explicit-any -import { Workflow, WorkflowFn } from "deco/blocks/workflow.ts"; -import { Arg, RuntimeParameters, WorkflowExecutionBase } from "deco/deps.ts"; -import { BlockFromKey, BlockFunc, BlockKeys } from "deco/engine/block.ts"; -import { Resolvable } from "deco/engine/core/resolver.ts"; -import { Context } from "deco/live.ts"; -import { AppManifest } from "deco/mod.ts"; import { start } from "../initializer.ts"; // side-effect initialize import { toExecution, WorkflowExecution, WorkflowMetadata } from "../types.ts"; - +import { Workflow, WorkflowFn } from "@deco/deco/blocks"; +import { Arg, RuntimeParameters, WorkflowExecutionBase } from "@deco/durable"; +import { + type AppManifest, + type BlockFromKey, + type BlockFunc, + type BlockKeys, + Context, + type Resolvable, +} from "@deco/deco"; export interface CommonProps< TMetadata extends WorkflowMetadata = WorkflowMetadata, > { @@ -23,19 +26,23 @@ export interface AnyWorkflow extends CommonProps { __resolveType: "resolved"; }; // TODO(mcandeia) generics is not working Resolved<Workflow>; } - export type WorkflowProps< key extends string = string, TManifest extends AppManifest = AppManifest, block extends BlockFromKey<key, TManifest> = BlockFromKey<key, TManifest>, > = key extends BlockKeys<TManifest> & `${string}/workflows/${string}` ? BlockFunc<key, TManifest, block> extends - WorkflowFn<infer TProps, any, infer TArgs> - ? TArgs["length"] extends 0 ? { key: key; props: TProps } & CommonProps - : { args: TArgs; key: key; props: TProps } & CommonProps + WorkflowFn<infer TProps, any, infer TArgs> ? TArgs["length"] extends 0 ? { + key: key; + props: TProps; + } & CommonProps + : { + args: TArgs; + key: key; + props: TProps; + } & CommonProps : AnyWorkflow : AnyWorkflow; - const fromWorkflowProps = < key extends string = string, TManifest extends AppManifest = AppManifest, @@ -50,10 +57,12 @@ const fromWorkflowProps = < ) { return anyProps.workflow; } - const wkflowProps = props as any as { key: string; props: any }; + const wkflowProps = props as any as { + key: string; + props: any; + }; return { ...(wkflowProps.props ?? {}), __resolveType: wkflowProps?.key }; }; - const WORKFLOW_QS = "workflow"; export const WorkflowQS = { buildFromProps: (workflow: ReturnType<typeof fromWorkflowProps>): string => { @@ -61,9 +70,7 @@ export const WorkflowQS = { encodeURIComponent(btoa(JSON.stringify(workflow))) }`; }, - extractFromUrl: ( - urlString: string, - ): Resolvable<Workflow> | undefined => { + extractFromUrl: (urlString: string): Resolvable<Workflow> | undefined => { const url = new URL(urlString); const qs = url.searchParams.get(WORKFLOW_QS); if (!qs) { @@ -89,11 +96,9 @@ export default async function startWorkflow< (context.isDeploy ? `wss://deco-sites-${context.site}-${context.deploymentId}.deno.dev` : "ws://localhost:8000"); - const url = new URL( `${service}/live/workflows/run?${WorkflowQS.buildFromProps(workflow)}`, ); - for ( const [key, value] of Object.entries( runtimeParameters?.websocket?.defaultQueryParams ?? {}, diff --git a/workflows/handlers/workflowRunner.ts b/workflows/handlers/workflowRunner.ts index c642980c8..004dd1831 100644 --- a/workflows/handlers/workflowRunner.ts +++ b/workflows/handlers/workflowRunner.ts @@ -1,12 +1,14 @@ import { HandlerContext } from "$fresh/server.ts"; -import { Handler } from "deco/blocks/handler.ts"; -import { Workflow, WorkflowContext } from "deco/blocks/workflow.ts"; -import { workflowHTTPHandler } from "deco/deps.ts"; -import { AppManifest, DecoSiteState, DecoState } from "deco/mod.ts"; +import { type Handler, Workflow, WorkflowContext } from "@deco/deco/blocks"; +import { workflowHTTPHandler } from "@deco/durable"; +import { + type AppManifest, + type DecoSiteState, + type DecoState, +} from "@deco/deco"; export interface Config { workflow: Workflow; } - export default function WorkflowHandler({ workflow }: Config): Handler { return (req: Request, conn: Deno.ServeHandlerInfo) => { const ctx = conn as unknown as HandlerContext< diff --git a/workflows/initializer.ts b/workflows/initializer.ts index 8502f4279..30d315929 100644 --- a/workflows/initializer.ts +++ b/workflows/initializer.ts @@ -1,4 +1,3 @@ -import { Context } from "deco/live.ts"; import { cancel as durableCancel, get as durableGet, @@ -6,7 +5,7 @@ import { signal as durableSignal, start as durableStart, } from "./deps.ts"; - +import { Context } from "@deco/deco"; const LOCAL_OPTIONS = { durableEndpoint: "http://localhost:8001", namespace: "x", @@ -28,7 +27,6 @@ const durableDefaultOpts = () => { }; return context.isDeploy ? remoteOptions : LOCAL_OPTIONS; }; - export const cancel: typeof durableCancel = (id, reason, opts) => { return durableCancel(id, reason, opts ?? durableDefaultOpts()); }; diff --git a/workflows/loaders/events.ts b/workflows/loaders/events.ts index f0a10bf0d..9ebb51b0a 100644 --- a/workflows/loaders/events.ts +++ b/workflows/loaders/events.ts @@ -1,17 +1,14 @@ -import type { HistoryEvent, Pagination } from "deco/deps.ts"; -import { StreamProps } from "deco/utils/invoke.ts"; -import { history } from "../initializer.ts"; // side-effect initialize - +import { history } from "../initializer.ts"; +import { HistoryEvent, Pagination } from "@deco/durable"; +import { StreamProps } from "@deco/deco"; export interface Props extends StreamProps { id: string; page?: number; pageSize?: number; } - export type Events = | Pagination<HistoryEvent> | AsyncIterableIterator<HistoryEvent>; - /** * @description Get the workflow execution events. */ diff --git a/workflows/loaders/get.ts b/workflows/loaders/get.ts index f0ebc3069..069d63f4e 100644 --- a/workflows/loaders/get.ts +++ b/workflows/loaders/get.ts @@ -1,10 +1,9 @@ -import { Arg } from "deco/deps.ts"; import { get } from "../initializer.ts"; // side-effect initialize import { toExecution, WorkflowExecution, WorkflowMetadata } from "../types.ts"; +import { Arg } from "@deco/durable"; export interface Props { id: string; } - /** * @description Read the workflow execution information. */ diff --git a/workflows/mod.ts b/workflows/mod.ts index 93e665555..e00c7c709 100644 --- a/workflows/mod.ts +++ b/workflows/mod.ts @@ -1,25 +1,19 @@ -import type { App, FnContext } from "deco/mod.ts"; import manifest, { Manifest } from "./manifest.gen.ts"; import { PreviewContainer } from "../utils/preview.tsx"; - +import { type App, type FnContext } from "@deco/deco"; // deno-lint-ignore ban-types export type State = {}; - /** * @title Deco Workflows * @description Build customized and automated tasks. * @category Tool * @logo https://raw.githubusercontent.com/deco-cx/apps/main/workflows/logo.png */ -export default function App( - state: State, -): App<Manifest, State> { +export default function App(state: State): App<Manifest, State> { return { manifest, state }; } - export type AppContext = FnContext<State, Manifest>; export type AppManifest = Manifest; - export const preview = () => { return { Component: PreviewContainer, diff --git a/workflows/utils/awaiters.ts b/workflows/utils/awaiters.ts index db37fbc8f..bb3e154f6 100644 --- a/workflows/utils/awaiters.ts +++ b/workflows/utils/awaiters.ts @@ -1,7 +1,6 @@ -import { type WorkflowContext } from "deco/mod.ts"; -import { isEventStreamResponse } from "deco/utils/invoke.ts"; import { type AppManifest } from "../mod.ts"; - +import { type WorkflowContext } from "@deco/deco/blocks"; +import { isEventStreamResponse } from "@deco/deco/web"; export const waitForWorkflowCompletion = <T extends AppManifest>( ctx: WorkflowContext<T>, id: string, @@ -9,16 +8,12 @@ export const waitForWorkflowCompletion = <T extends AppManifest>( ) => ctx.callLocalActivity(async () => { const events = await (ctx as unknown as WorkflowContext<AppManifest>).state - .invoke( - "workflows/loaders/events.ts", - { stream: true, id }, - ); - - if (!isEventStreamResponse(events)) return; - + .invoke("workflows/loaders/events.ts", { stream: true, id }); + if (!isEventStreamResponse(events)) { + return; + } if (timeoutMS) { setTimeout(() => events.return?.(), timeoutMS); } - for await (const _event of events); });