threeu-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -0
- package/dist/admin/index.d.ts +22 -0
- package/dist/admin/index.js +17 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/chunk-2TGBSV6L.js +197 -0
- package/dist/chunk-2TGBSV6L.js.map +1 -0
- package/dist/chunk-6S7OFN23.js +81 -0
- package/dist/chunk-6S7OFN23.js.map +1 -0
- package/dist/chunk-6ZCBDWWQ.js +140 -0
- package/dist/chunk-6ZCBDWWQ.js.map +1 -0
- package/dist/chunk-BCUODRZW.js +78 -0
- package/dist/chunk-BCUODRZW.js.map +1 -0
- package/dist/chunk-H3XILKGI.js +70 -0
- package/dist/chunk-H3XILKGI.js.map +1 -0
- package/dist/chunk-HYSJ6YPN.js +425 -0
- package/dist/chunk-HYSJ6YPN.js.map +1 -0
- package/dist/chunk-LFF5LPWT.js +122 -0
- package/dist/chunk-LFF5LPWT.js.map +1 -0
- package/dist/chunk-OXAQGEMQ.js +3 -0
- package/dist/chunk-OXAQGEMQ.js.map +1 -0
- package/dist/chunk-USFJIM5K.js +195 -0
- package/dist/chunk-USFJIM5K.js.map +1 -0
- package/dist/chunk-ZWLFJIAM.js +69 -0
- package/dist/chunk-ZWLFJIAM.js.map +1 -0
- package/dist/define-B6ZJMWDI.d.ts +24 -0
- package/dist/developer/index.d.ts +120 -0
- package/dist/developer/index.js +106 -0
- package/dist/developer/index.js.map +1 -0
- package/dist/errors/index.d.ts +83 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index-ksmDFDZc.d.ts +90 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/index.d.ts +13 -0
- package/dist/plugin/index.js +27 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/server/index.d.ts +70 -0
- package/dist/plugin/server/index.js +105 -0
- package/dist/plugin/server/index.js.map +1 -0
- package/dist/pos/index.d.ts +26 -0
- package/dist/pos/index.js +24 -0
- package/dist/pos/index.js.map +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.js +7 -0
- package/dist/react/index.js.map +1 -0
- package/dist/session-BFDRm-KJ.d.ts +94 -0
- package/dist/storefront/index.d.ts +76 -0
- package/dist/storefront/index.js +6 -0
- package/dist/storefront/index.js.map +1 -0
- package/dist/testing/index.d.ts +82 -0
- package/dist/testing/index.js +106 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/theme/index.d.ts +109 -0
- package/dist/theme/index.js +152 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/threeu-BesFjXdw.d.ts +143 -0
- package/dist/types-BoGD3IXz.d.ts +173 -0
- package/dist/types-DfyYnoLn.d.ts +248 -0
- package/dist/webhooks/index.d.ts +36 -0
- package/dist/webhooks/index.js +4 -0
- package/dist/webhooks/index.js.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ThreeuStorefront } from './chunk-6S7OFN23.js';
|
|
2
|
+
import { createContext, useMemo, useContext, useState, useEffect } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/theme/runtime.ts
|
|
6
|
+
var registry = /* @__PURE__ */ new Map();
|
|
7
|
+
var activeCustomization = {};
|
|
8
|
+
function registerField(name, kind, defaultValue, label) {
|
|
9
|
+
const existing = registry.get(name);
|
|
10
|
+
if (existing) {
|
|
11
|
+
if (existing.default === void 0 && defaultValue !== void 0) existing.default = defaultValue;
|
|
12
|
+
if (!existing.label && label) existing.label = label;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
registry.set(name, { name, kind, default: defaultValue, label });
|
|
16
|
+
}
|
|
17
|
+
function resolveValue(name, fallback) {
|
|
18
|
+
if (name in activeCustomization) return activeCustomization[name];
|
|
19
|
+
const reg = registry.get(name);
|
|
20
|
+
if (reg && reg.default !== void 0) return reg.default;
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
function setActiveCustomization(values) {
|
|
24
|
+
activeCustomization = values ?? {};
|
|
25
|
+
}
|
|
26
|
+
function extractFields() {
|
|
27
|
+
return Array.from(registry.values()).map((f) => ({
|
|
28
|
+
name: f.name,
|
|
29
|
+
kind: f.kind,
|
|
30
|
+
...f.default !== void 0 ? { default: f.default } : {},
|
|
31
|
+
...f.label ? { label: f.label } : {}
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
function resetRegistry() {
|
|
35
|
+
registry.clear();
|
|
36
|
+
activeCustomization = {};
|
|
37
|
+
}
|
|
38
|
+
var ThemeContext = createContext(null);
|
|
39
|
+
function ThemeProvider(props) {
|
|
40
|
+
setActiveCustomization(props.customization);
|
|
41
|
+
const value = useMemo(
|
|
42
|
+
() => ({
|
|
43
|
+
customization: props.customization ?? {},
|
|
44
|
+
brand: props.brand,
|
|
45
|
+
locale: props.locale ?? "en",
|
|
46
|
+
currency: props.currency ?? "QAR",
|
|
47
|
+
preview: props.preview ?? false,
|
|
48
|
+
storefront: props.storefront
|
|
49
|
+
}),
|
|
50
|
+
[props.customization, props.brand, props.locale, props.currency, props.preview, props.storefront]
|
|
51
|
+
);
|
|
52
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children: props.children });
|
|
53
|
+
}
|
|
54
|
+
function ThreeuThemeProvider(props) {
|
|
55
|
+
const { publicToken, baseUrl, brand, ...rest } = props;
|
|
56
|
+
const storefront = useMemo(
|
|
57
|
+
() => brand ? new ThreeuStorefront({ brand: brand.id, publicToken, baseUrl }) : void 0,
|
|
58
|
+
[brand, publicToken, baseUrl]
|
|
59
|
+
);
|
|
60
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { ...rest, brand, storefront, children: props.children });
|
|
61
|
+
}
|
|
62
|
+
function useThemeContext() {
|
|
63
|
+
const ctx = useContext(ThemeContext);
|
|
64
|
+
if (!ctx) {
|
|
65
|
+
throw new Error("ThreeU theme hooks must be used inside a <ThemeProvider> / <ThreeuThemeProvider>.");
|
|
66
|
+
}
|
|
67
|
+
return ctx;
|
|
68
|
+
}
|
|
69
|
+
function useTheme() {
|
|
70
|
+
return useThemeContext();
|
|
71
|
+
}
|
|
72
|
+
function useBrand() {
|
|
73
|
+
return useThemeContext().brand;
|
|
74
|
+
}
|
|
75
|
+
function useLocale() {
|
|
76
|
+
return useThemeContext().locale;
|
|
77
|
+
}
|
|
78
|
+
function useCurrency() {
|
|
79
|
+
return useThemeContext().currency;
|
|
80
|
+
}
|
|
81
|
+
function useThemeField(name, kind, defaultValue) {
|
|
82
|
+
const { customization } = useThemeContext();
|
|
83
|
+
registerField(name, kind, defaultValue);
|
|
84
|
+
if (name in customization) return customization[name];
|
|
85
|
+
return resolveValue(name, defaultValue);
|
|
86
|
+
}
|
|
87
|
+
function useStorefront() {
|
|
88
|
+
const { storefront } = useThemeContext();
|
|
89
|
+
if (!storefront) {
|
|
90
|
+
throw new Error("This hook needs a storefront client. Use <ThreeuThemeProvider> or pass `storefront` to <ThemeProvider>.");
|
|
91
|
+
}
|
|
92
|
+
return storefront;
|
|
93
|
+
}
|
|
94
|
+
function useAsync(fn, deps) {
|
|
95
|
+
const [state, setState] = useState({ data: void 0, loading: true, error: void 0 });
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
let active = true;
|
|
98
|
+
setState((s) => ({ ...s, loading: true, error: void 0 }));
|
|
99
|
+
fn().then((data) => active && setState({ data, loading: false, error: void 0 })).catch((error) => active && setState({ data: void 0, loading: false, error }));
|
|
100
|
+
return () => {
|
|
101
|
+
active = false;
|
|
102
|
+
};
|
|
103
|
+
}, deps);
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
function useProducts(params) {
|
|
107
|
+
const sf = useStorefront();
|
|
108
|
+
return useAsync(async () => (await sf.products.list(params)).items, [params?.collection, params?.limit]);
|
|
109
|
+
}
|
|
110
|
+
function useCollections(params) {
|
|
111
|
+
const sf = useStorefront();
|
|
112
|
+
return useAsync(async () => (await sf.collections.list(params)).items, [params?.limit]);
|
|
113
|
+
}
|
|
114
|
+
function useCart() {
|
|
115
|
+
const sf = useStorefront();
|
|
116
|
+
const [cart, setCart] = useState(void 0);
|
|
117
|
+
const create = useMemo(
|
|
118
|
+
() => async () => {
|
|
119
|
+
const created = await sf.cart.create();
|
|
120
|
+
setCart(created);
|
|
121
|
+
return created;
|
|
122
|
+
},
|
|
123
|
+
[sf]
|
|
124
|
+
);
|
|
125
|
+
return { cart, create };
|
|
126
|
+
}
|
|
127
|
+
function useCustomer() {
|
|
128
|
+
return { customer: void 0 };
|
|
129
|
+
}
|
|
130
|
+
function useCheckout() {
|
|
131
|
+
return {
|
|
132
|
+
checkout: async () => {
|
|
133
|
+
throw new Error("useCheckout: wire a checkout handler in your theme; the storefront checkout flow is host-defined.");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { ThemeProvider, ThreeuThemeProvider, extractFields, registerField, resetRegistry, resolveValue, setActiveCustomization, useBrand, useCart, useCheckout, useCollections, useCurrency, useCustomer, useLocale, useProducts, useTheme, useThemeContext, useThemeField };
|
|
139
|
+
//# sourceMappingURL=chunk-6ZCBDWWQ.js.map
|
|
140
|
+
//# sourceMappingURL=chunk-6ZCBDWWQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme/runtime.ts","../src/theme/context.tsx"],"names":[],"mappings":"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","file":"chunk-6ZCBDWWQ.js","sourcesContent":["/**\n * Theme runtime: the editable-field registry + active customization store.\n *\n * `T*` components and `TColor()` register the fields they expose (so a build step\n * / the theme editor can enumerate every customizable field) and resolve their\n * current value from the active customization map that `ThemeProvider` installs.\n *\n * The active store is module-level so the non-hook `TColor(name, default)`\n * helper can resolve values during render. This is intentionally simple and is\n * not concurrent-render safe; SSR/streaming themes should read values via the\n * `useThemeField` hook instead.\n */\nimport type { ThemeEditableField } from \"../manifests/types\";\n\nexport type FieldKind = ThemeEditableField[\"kind\"];\n\ninterface RegisteredField {\n name: string;\n kind: FieldKind;\n default?: unknown;\n label?: string;\n}\n\nconst registry = new Map<string, RegisteredField>();\nlet activeCustomization: Record<string, unknown> = {};\n\n/** Register (or update) an editable field. Idempotent by `name`. */\nexport function registerField(name: string, kind: FieldKind, defaultValue?: unknown, label?: string): void {\n const existing = registry.get(name);\n if (existing) {\n if (existing.default === undefined && defaultValue !== undefined) existing.default = defaultValue;\n if (!existing.label && label) existing.label = label;\n return;\n }\n registry.set(name, { name, kind, default: defaultValue, label });\n}\n\n/** Resolve a field's current value: customization override → registered default. */\nexport function resolveValue<T = unknown>(name: string, fallback?: T): T {\n if (name in activeCustomization) return activeCustomization[name] as T;\n const reg = registry.get(name);\n if (reg && reg.default !== undefined) return reg.default as T;\n return fallback as T;\n}\n\n/** Install the active customization map (called by ThemeProvider during render). */\nexport function setActiveCustomization(values: Record<string, unknown> | undefined): void {\n activeCustomization = values ?? {};\n}\n\n/** Snapshot every registered field — used for manifest generation / extraction. */\nexport function extractFields(): ThemeEditableField[] {\n return Array.from(registry.values()).map((f) => ({\n name: f.name,\n kind: f.kind,\n ...(f.default !== undefined ? { default: f.default } : {}),\n ...(f.label ? { label: f.label } : {}),\n }));\n}\n\n/** Clear the registry (tests / hot reload). */\nexport function resetRegistry(): void {\n registry.clear();\n activeCustomization = {};\n}\n","/**\n * Theme React context: provider + data/customization hooks.\n *\n * `ThemeProvider` installs the active customization and exposes brand/locale/\n * currency plus an optional `ThreeuStorefront` that the data hooks\n * (`useProducts`, `useCart`, ...) read from. `react` is an optional peer.\n */\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\nimport { ThreeuStorefront } from \"../storefront\";\nimport type { Collection, Id, Product } from \"../resources/types\";\nimport { registerField, resolveValue, setActiveCustomization, type FieldKind } from \"./runtime\";\n\nexport interface ThemeBrandInfo {\n id: Id;\n name?: string;\n [key: string]: unknown;\n}\n\nexport interface ThemeContextValue {\n customization: Record<string, unknown>;\n brand?: ThemeBrandInfo;\n locale: string;\n currency: string;\n preview: boolean;\n storefront?: ThreeuStorefront;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n customization?: Record<string, unknown>;\n brand?: ThemeBrandInfo;\n locale?: string;\n currency?: string;\n preview?: boolean;\n storefront?: ThreeuStorefront;\n}\n\nexport function ThemeProvider(props: ThemeProviderProps): JSX.Element {\n // Install active customization synchronously so non-hook TColor() resolves\n // correctly for descendants rendered in the same pass.\n setActiveCustomization(props.customization);\n\n const value = useMemo<ThemeContextValue>(\n () => ({\n customization: props.customization ?? {},\n brand: props.brand,\n locale: props.locale ?? \"en\",\n currency: props.currency ?? \"QAR\",\n preview: props.preview ?? false,\n storefront: props.storefront,\n }),\n [props.customization, props.brand, props.locale, props.currency, props.preview, props.storefront],\n );\n\n return <ThemeContext.Provider value={value}>{props.children}</ThemeContext.Provider>;\n}\n\nexport interface ThreeuThemeProviderProps extends Omit<ThemeProviderProps, \"storefront\"> {\n publicToken?: string;\n baseUrl?: string;\n}\n\n/** Convenience provider that builds a storefront client from a public token. */\nexport function ThreeuThemeProvider(props: ThreeuThemeProviderProps): JSX.Element {\n const { publicToken, baseUrl, brand, ...rest } = props;\n const storefront = useMemo(\n () =>\n brand\n ? new ThreeuStorefront({ brand: brand.id, publicToken, baseUrl })\n : undefined,\n [brand, publicToken, baseUrl],\n );\n return (\n <ThemeProvider {...rest} brand={brand} storefront={storefront}>\n {props.children}\n </ThemeProvider>\n );\n}\n\nexport function useThemeContext(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) {\n throw new Error(\"ThreeU theme hooks must be used inside a <ThemeProvider> / <ThreeuThemeProvider>.\");\n }\n return ctx;\n}\n\nexport function useTheme(): ThemeContextValue {\n return useThemeContext();\n}\n\nexport function useBrand(): ThemeBrandInfo | undefined {\n return useThemeContext().brand;\n}\n\nexport function useLocale(): string {\n return useThemeContext().locale;\n}\n\nexport function useCurrency(): string {\n return useThemeContext().currency;\n}\n\n/** SSR/concurrent-safe field read: registers the field and returns its value. */\nexport function useThemeField<T = unknown>(name: string, kind: FieldKind, defaultValue?: T): T {\n const { customization } = useThemeContext();\n registerField(name, kind, defaultValue);\n if (name in customization) return customization[name] as T;\n return resolveValue<T>(name, defaultValue);\n}\n\n// ---------------------------------------------------------------------------\n// Data hooks\n// ---------------------------------------------------------------------------\n\nexport interface AsyncState<T> {\n data: T | undefined;\n loading: boolean;\n error: Error | undefined;\n}\n\nfunction useStorefront(): ThreeuStorefront {\n const { storefront } = useThemeContext();\n if (!storefront) {\n throw new Error(\"This hook needs a storefront client. Use <ThreeuThemeProvider> or pass `storefront` to <ThemeProvider>.\");\n }\n return storefront;\n}\n\nfunction useAsync<T>(fn: () => Promise<T>, deps: unknown[]): AsyncState<T> {\n const [state, setState] = useState<AsyncState<T>>({ data: undefined, loading: true, error: undefined });\n useEffect(() => {\n let active = true;\n setState((s) => ({ ...s, loading: true, error: undefined }));\n fn()\n .then((data) => active && setState({ data, loading: false, error: undefined }))\n .catch((error: Error) => active && setState({ data: undefined, loading: false, error }));\n return () => {\n active = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n return state;\n}\n\nexport function useProducts(params?: { collection?: string; limit?: number }): AsyncState<Product[]> {\n const sf = useStorefront();\n return useAsync(async () => (await sf.products.list(params)).items, [params?.collection, params?.limit]);\n}\n\nexport function useCollections(params?: { limit?: number }): AsyncState<Collection[]> {\n const sf = useStorefront();\n return useAsync(async () => (await sf.collections.list(params)).items, [params?.limit]);\n}\n\nexport function useCart(): {\n cart: Record<string, unknown> | undefined;\n create: () => Promise<Record<string, unknown>>;\n} {\n const sf = useStorefront();\n const [cart, setCart] = useState<Record<string, unknown> | undefined>(undefined);\n const create = useMemo(\n () => async () => {\n const created = await sf.cart.create();\n setCart(created);\n return created;\n },\n [sf],\n );\n return { cart, create };\n}\n\nexport function useCustomer(): { customer: Record<string, unknown> | undefined } {\n // Customer identity is host-app supplied; exposed for parity with the brief.\n return { customer: undefined };\n}\n\nexport function useCheckout(): { checkout: (input: Record<string, unknown>) => Promise<void> } {\n return {\n checkout: async () => {\n throw new Error(\"useCheckout: wire a checkout handler in your theme; the storefront checkout flow is host-defined.\");\n },\n };\n}\n\nexport { ThemeContext };\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/pos/session.ts
|
|
2
|
+
var PosCart = class {
|
|
3
|
+
constructor(ctx, id, data) {
|
|
4
|
+
this.ctx = ctx;
|
|
5
|
+
this.id = id;
|
|
6
|
+
this.data = data;
|
|
7
|
+
}
|
|
8
|
+
opts(body) {
|
|
9
|
+
return this.ctx.brand !== void 0 ? { brand: this.ctx.brand, body, idempotent: true } : { body, idempotent: true };
|
|
10
|
+
}
|
|
11
|
+
get snapshot() {
|
|
12
|
+
return this.data;
|
|
13
|
+
}
|
|
14
|
+
async addItem(item) {
|
|
15
|
+
this.data = await this.ctx.http.post(
|
|
16
|
+
`pos/carts/${this.id}/items`,
|
|
17
|
+
this.opts({ product_id: item.productId, quantity: item.quantity, variant_id: item.variantId, price: item.price })
|
|
18
|
+
);
|
|
19
|
+
return this.data;
|
|
20
|
+
}
|
|
21
|
+
async removeItem(itemId) {
|
|
22
|
+
this.data = await this.ctx.http.delete(
|
|
23
|
+
`pos/carts/${this.id}/items/${itemId}`,
|
|
24
|
+
this.ctx.brand !== void 0 ? { brand: this.ctx.brand } : {}
|
|
25
|
+
);
|
|
26
|
+
return this.data;
|
|
27
|
+
}
|
|
28
|
+
async applyDiscount(discount) {
|
|
29
|
+
this.data = await this.ctx.http.post(`pos/carts/${this.id}/discounts`, this.opts(discount));
|
|
30
|
+
return this.data;
|
|
31
|
+
}
|
|
32
|
+
async checkout(input) {
|
|
33
|
+
return await this.ctx.http.post(
|
|
34
|
+
`pos/carts/${this.id}/checkout`,
|
|
35
|
+
this.opts({ method: input.method, amount_tendered: input.amountTendered, customer_id: input.customerId })
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var PosCartNamespace = class {
|
|
40
|
+
constructor(ctx, config) {
|
|
41
|
+
this.ctx = ctx;
|
|
42
|
+
this.config = config;
|
|
43
|
+
}
|
|
44
|
+
async create() {
|
|
45
|
+
const body = { location_id: this.config.locationId, cashier_id: this.config.cashierId };
|
|
46
|
+
const data = await this.ctx.http.post(
|
|
47
|
+
"pos/carts",
|
|
48
|
+
this.ctx.brand !== void 0 ? { brand: this.ctx.brand, body, idempotent: true } : { body, idempotent: true }
|
|
49
|
+
);
|
|
50
|
+
return new PosCart(this.ctx, data.id, data);
|
|
51
|
+
}
|
|
52
|
+
async get(id) {
|
|
53
|
+
const data = await this.ctx.http.get(
|
|
54
|
+
`pos/carts/${id}`,
|
|
55
|
+
this.ctx.brand !== void 0 ? { brand: this.ctx.brand } : {}
|
|
56
|
+
);
|
|
57
|
+
return new PosCart(this.ctx, data.id, data);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var PosSession = class {
|
|
61
|
+
constructor(ctx, config) {
|
|
62
|
+
this.ctx = ctx;
|
|
63
|
+
this.config = config;
|
|
64
|
+
this.cart = new PosCartNamespace(ctx, config);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var PosNamespace = class {
|
|
68
|
+
constructor(ctx) {
|
|
69
|
+
this.ctx = ctx;
|
|
70
|
+
}
|
|
71
|
+
session(config = {}) {
|
|
72
|
+
return new PosSession(this.ctx, config);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { PosCart, PosCartNamespace, PosNamespace, PosSession };
|
|
77
|
+
//# sourceMappingURL=chunk-BCUODRZW.js.map
|
|
78
|
+
//# sourceMappingURL=chunk-BCUODRZW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pos/session.ts"],"names":[],"mappings":"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","file":"chunk-BCUODRZW.js","sourcesContent":["/**\n * POS session + cart. A session captures the location/cashier context; carts are\n * created within it and drive the add-item → discount → checkout flow.\n * Shared by `brand.pos.session(...)` and the standalone `ThreeuPOS` client.\n */\nimport type { ResourceContext } from \"../resources/resource\";\nimport type { Id } from \"../resources/types\";\n\nexport interface PosSessionConfig {\n locationId?: Id;\n cashierId?: Id;\n}\n\nexport interface CartItemInput {\n productId: Id;\n quantity: number;\n variantId?: Id;\n price?: number;\n}\n\nexport interface CartDiscountInput {\n type: \"percentage\" | \"fixed\";\n value: number;\n code?: string;\n}\n\nexport interface CheckoutInput {\n method: \"card\" | \"cash\" | \"wallet\" | string;\n amountTendered?: number;\n customerId?: Id;\n}\n\nexport interface CartData {\n id: Id;\n [key: string]: unknown;\n}\n\nexport class PosCart {\n constructor(\n private readonly ctx: ResourceContext,\n readonly id: Id,\n private data: CartData,\n ) {}\n\n private opts(body?: unknown) {\n return this.ctx.brand !== undefined ? { brand: this.ctx.brand, body, idempotent: true } : { body, idempotent: true };\n }\n\n get snapshot(): CartData {\n return this.data;\n }\n\n async addItem(item: CartItemInput): Promise<CartData> {\n this.data = (await this.ctx.http.post(\n `pos/carts/${this.id}/items`,\n this.opts({ product_id: item.productId, quantity: item.quantity, variant_id: item.variantId, price: item.price }),\n )) as CartData;\n return this.data;\n }\n\n async removeItem(itemId: Id): Promise<CartData> {\n this.data = (await this.ctx.http.delete(\n `pos/carts/${this.id}/items/${itemId}`,\n this.ctx.brand !== undefined ? { brand: this.ctx.brand } : {},\n )) as CartData;\n return this.data;\n }\n\n async applyDiscount(discount: CartDiscountInput): Promise<CartData> {\n this.data = (await this.ctx.http.post(`pos/carts/${this.id}/discounts`, this.opts(discount))) as CartData;\n return this.data;\n }\n\n async checkout(input: CheckoutInput): Promise<Record<string, unknown>> {\n return (await this.ctx.http.post(\n `pos/carts/${this.id}/checkout`,\n this.opts({ method: input.method, amount_tendered: input.amountTendered, customer_id: input.customerId }),\n )) as Record<string, unknown>;\n }\n}\n\nexport class PosCartNamespace {\n constructor(\n private readonly ctx: ResourceContext,\n private readonly config: PosSessionConfig,\n ) {}\n\n async create(): Promise<PosCart> {\n const body = { location_id: this.config.locationId, cashier_id: this.config.cashierId };\n const data = (await this.ctx.http.post(\n \"pos/carts\",\n this.ctx.brand !== undefined ? { brand: this.ctx.brand, body, idempotent: true } : { body, idempotent: true },\n )) as CartData;\n return new PosCart(this.ctx, data.id, data);\n }\n\n async get(id: Id): Promise<PosCart> {\n const data = (await this.ctx.http.get(\n `pos/carts/${id}`,\n this.ctx.brand !== undefined ? { brand: this.ctx.brand } : {},\n )) as CartData;\n return new PosCart(this.ctx, data.id, data);\n }\n}\n\nexport class PosSession {\n readonly cart: PosCartNamespace;\n constructor(\n private readonly ctx: ResourceContext,\n readonly config: PosSessionConfig,\n ) {\n this.cart = new PosCartNamespace(ctx, config);\n }\n}\n\n/** Factory used by both the brand POS namespace and ThreeuPOS. */\nexport class PosNamespace {\n constructor(private readonly ctx: ResourceContext) {}\n session(config: PosSessionConfig = {}): PosSession {\n return new PosSession(this.ctx, config);\n }\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/core/runtime.ts
|
|
2
|
+
var cached;
|
|
3
|
+
function detectRuntime() {
|
|
4
|
+
if (cached) return cached;
|
|
5
|
+
cached = computeRuntime();
|
|
6
|
+
return cached;
|
|
7
|
+
}
|
|
8
|
+
function computeRuntime() {
|
|
9
|
+
if (typeof EdgeRuntime === "string") return "edge";
|
|
10
|
+
const proc = globalThis.process;
|
|
11
|
+
if (proc?.versions?.node) return "node";
|
|
12
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") return "browser";
|
|
13
|
+
const maybeImportScripts = globalThis.importScripts;
|
|
14
|
+
if (typeof self !== "undefined" && typeof maybeImportScripts === "function") return "browser";
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
|
17
|
+
function isBrowserLike(runtime = detectRuntime()) {
|
|
18
|
+
return runtime === "browser";
|
|
19
|
+
}
|
|
20
|
+
function isDevelopment() {
|
|
21
|
+
const proc = globalThis.process;
|
|
22
|
+
const nodeEnv = proc?.env?.NODE_ENV;
|
|
23
|
+
if (nodeEnv) return nodeEnv !== "production";
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function hasGlobalFetch() {
|
|
27
|
+
return typeof globalThis.fetch === "function";
|
|
28
|
+
}
|
|
29
|
+
function resolveFetch(custom) {
|
|
30
|
+
if (custom) return custom;
|
|
31
|
+
if (hasGlobalFetch()) return globalThis.fetch.bind(globalThis);
|
|
32
|
+
throw new Error(
|
|
33
|
+
"No global fetch available in this runtime. Pass a `fetch` implementation via client options."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/core/types.ts
|
|
38
|
+
var SECRET_TOKEN_TYPES = /* @__PURE__ */ new Set([
|
|
39
|
+
"secret_admin",
|
|
40
|
+
"plugin_installation",
|
|
41
|
+
"developer"
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// src/core/safety.ts
|
|
45
|
+
var seen = /* @__PURE__ */ new Set();
|
|
46
|
+
function warnOnce(key, message) {
|
|
47
|
+
if (seen.has(key)) return;
|
|
48
|
+
seen.add(key);
|
|
49
|
+
console.warn(`[@threeu/threeu] ${message}`);
|
|
50
|
+
}
|
|
51
|
+
function runClientSafetyChecks(ctx) {
|
|
52
|
+
if (ctx.suppressWarnings) return;
|
|
53
|
+
const runtime = ctx.runtime ?? detectRuntime();
|
|
54
|
+
if (ctx.tokenType && SECRET_TOKEN_TYPES.has(ctx.tokenType) && isBrowserLike(runtime)) {
|
|
55
|
+
warnOnce(
|
|
56
|
+
`secret-token-browser:${ctx.tokenType}`,
|
|
57
|
+
`A "${ctx.tokenType}" token is being used in a browser-like runtime. Secret/admin/developer tokens must stay server-side \u2014 use a public storefront token in the browser instead.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (ctx.baseUrl && ctx.baseUrl.startsWith("http://") && !isDevelopment()) {
|
|
61
|
+
warnOnce(
|
|
62
|
+
`insecure-base-url:${ctx.baseUrl}`,
|
|
63
|
+
`Insecure base URL "${ctx.baseUrl}" in production. Use https://.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { SECRET_TOKEN_TYPES, detectRuntime, isBrowserLike, isDevelopment, resolveFetch, runClientSafetyChecks, warnOnce };
|
|
69
|
+
//# sourceMappingURL=chunk-H3XILKGI.js.map
|
|
70
|
+
//# sourceMappingURL=chunk-H3XILKGI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/runtime.ts","../src/core/types.ts","../src/core/safety.ts"],"names":[],"mappings":";AAaA,IAAI,MAAA;AAEG,SAAS,aAAA,GAA+B;AAC7C,EAAA,IAAI,QAAQ,OAAO,MAAA;AACnB,EAAA,MAAA,GAAS,cAAA,EAAe;AACxB,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,cAAA,GAAgC;AAEvC,EAAA,IAAI,OAAO,WAAA,KAAgB,QAAA,EAAU,OAAO,MAAA;AAG5C,EAAA,MAAM,OAAQ,UAAA,CAA8D,OAAA;AAC5E,EAAA,IAAI,IAAA,EAAM,QAAA,EAAU,IAAA,EAAM,OAAO,MAAA;AAGjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,QAAA,KAAa,aAAa,OAAO,SAAA;AAG7E,EAAA,MAAM,qBAAsB,UAAA,CAA2C,aAAA;AACvE,EAAA,IAAI,OAAO,IAAA,KAAS,WAAA,IAAe,OAAO,kBAAA,KAAuB,YAAY,OAAO,SAAA;AAEpF,EAAA,OAAO,SAAA;AACT;AAGO,SAAS,aAAA,CAAc,OAAA,GAAyB,aAAA,EAAc,EAAY;AAC/E,EAAA,OAAO,OAAA,KAAY,SAAA;AACrB;AAGO,SAAS,aAAA,GAAyB;AACvC,EAAA,MAAM,OAAQ,UAAA,CAA0E,OAAA;AACxF,EAAA,MAAM,OAAA,GAAU,MAAM,GAAA,EAAK,QAAA;AAC3B,EAAA,IAAI,OAAA,SAAgB,OAAA,KAAY,YAAA;AAEhC,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,cAAA,GAA0B;AACxC,EAAA,OAAO,OAAO,WAAW,KAAA,KAAU,UAAA;AACrC;AAGO,SAAS,aAAa,MAAA,EAAqC;AAChE,EAAA,IAAI,QAAQ,OAAO,MAAA;AACnB,EAAA,IAAI,gBAAe,EAAG,OAAO,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AAC7D,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;;;ACjDO,IAAM,kBAAA,uBAAiD,GAAA,CAAe;AAAA,EAC3E,cAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAC;;;ACXD,IAAM,IAAA,uBAAW,GAAA,EAAY;AAGtB,SAAS,QAAA,CAAS,KAAa,OAAA,EAAuB;AAC3D,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AACnB,EAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AAEZ,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,iBAAA,EAAoB,OAAO,CAAA,CAAE,CAAA;AAC5C;AAeO,SAAS,sBAAsB,GAAA,EAA0B;AAC9D,EAAA,IAAI,IAAI,gBAAA,EAAkB;AAC1B,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,IAAW,aAAA,EAAc;AAE7C,EAAA,IAAI,GAAA,CAAI,aAAa,kBAAA,CAAmB,GAAA,CAAI,IAAI,SAAS,CAAA,IAAK,aAAA,CAAc,OAAO,CAAA,EAAG;AACpF,IAAA,QAAA;AAAA,MACE,CAAA,qBAAA,EAAwB,IAAI,SAAS,CAAA,CAAA;AAAA,MACrC,CAAA,GAAA,EAAM,IAAI,SAAS,CAAA,iKAAA;AAAA,KACrB;AAAA,EACF;AAEA,EAAA,IAAI,GAAA,CAAI,WAAW,GAAA,CAAI,OAAA,CAAQ,WAAW,SAAS,CAAA,IAAK,CAAC,aAAA,EAAc,EAAG;AACxE,IAAA,QAAA;AAAA,MACE,CAAA,kBAAA,EAAqB,IAAI,OAAO,CAAA,CAAA;AAAA,MAChC,CAAA,mBAAA,EAAsB,IAAI,OAAO,CAAA,8BAAA;AAAA,KACnC;AAAA,EACF;AACF","file":"chunk-H3XILKGI.js","sourcesContent":["/**\n * Runtime detection + capability probes.\n *\n * The SDK behaves differently per runtime: it refuses to attach secret/admin\n * tokens in a browser, picks Web Crypto vs node:crypto for webhook signing, and\n * tags telemetry with the runtime name.\n */\n\nexport type ThreeuRuntime = \"browser\" | \"node\" | \"edge\" | \"unknown\";\n\ndeclare const EdgeRuntime: unknown;\n\n/** Best-effort detection of the current JS runtime. Cached after first call. */\nlet cached: ThreeuRuntime | undefined;\n\nexport function detectRuntime(): ThreeuRuntime {\n if (cached) return cached;\n cached = computeRuntime();\n return cached;\n}\n\nfunction computeRuntime(): ThreeuRuntime {\n // Vercel/Cloudflare-style edge runtimes expose a global EdgeRuntime marker.\n if (typeof EdgeRuntime === \"string\") return \"edge\";\n\n // Node: `process.versions.node` is the most reliable signal.\n const proc = (globalThis as { process?: { versions?: { node?: string } } }).process;\n if (proc?.versions?.node) return \"node\";\n\n // Browser / DOM environments.\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") return \"browser\";\n\n // Web Worker / service worker.\n const maybeImportScripts = (globalThis as { importScripts?: unknown }).importScripts;\n if (typeof self !== \"undefined\" && typeof maybeImportScripts === \"function\") return \"browser\";\n\n return \"unknown\";\n}\n\n/** True in environments where exposing a secret token is a security risk. */\nexport function isBrowserLike(runtime: ThreeuRuntime = detectRuntime()): boolean {\n return runtime === \"browser\";\n}\n\n/** True when running in development (used to gate dev-only safety warnings). */\nexport function isDevelopment(): boolean {\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n const nodeEnv = proc?.env?.NODE_ENV;\n if (nodeEnv) return nodeEnv !== \"production\";\n // No process.env (browser) — assume dev so warnings surface during local builds.\n return true;\n}\n\n/** Whether a global `fetch` is available without the caller supplying one. */\nexport function hasGlobalFetch(): boolean {\n return typeof globalThis.fetch === \"function\";\n}\n\n/** Resolve the fetch implementation, preferring an explicitly supplied one. */\nexport function resolveFetch(custom?: typeof fetch): typeof fetch {\n if (custom) return custom;\n if (hasGlobalFetch()) return globalThis.fetch.bind(globalThis);\n throw new Error(\n \"No global fetch available in this runtime. Pass a `fetch` implementation via client options.\",\n );\n}\n","import type { ThreeuRuntime } from \"./runtime\";\n\n/**\n * Token classes the SDK understands. Each maps to a backend auth context and a\n * set of safety rules (e.g. `secret_admin` must never run in a browser).\n * See docs/ARCHITECTURE_MAP.md §9.\n */\nexport type TokenType =\n | \"secret_admin\"\n | \"public_storefront\"\n | \"plugin_installation\"\n | \"pos\"\n | \"theme_dev\"\n | \"developer\";\n\n/** Token types that grant privileged/secret access and must stay server-side. */\nexport const SECRET_TOKEN_TYPES: ReadonlySet<TokenType> = new Set<TokenType>([\n \"secret_admin\",\n \"plugin_installation\",\n \"developer\",\n]);\n\nexport interface ThreeuAppInfo {\n name?: string;\n version?: string;\n}\n\nexport interface ThreeuTelemetryOptions {\n enabled?: boolean;\n endpoint?: string;\n sampleRate?: number;\n flushIntervalMs?: number;\n maxBatchSize?: number;\n}\n\n/** Options shared by every client (core, admin, pos, storefront, developer). */\nexport interface ThreeuClientOptions {\n baseUrl?: string;\n token?: string;\n tokenType?: TokenType;\n /** Brand/tenant id sent as the `platform-id` header (and `X-Brand-ID` alias). */\n brand?: string | number;\n timeoutMs?: number;\n retries?: number;\n /** Custom fetch (e.g. undici, node-fetch, a mock). Defaults to global fetch. */\n fetch?: typeof fetch;\n logger?: ThreeuLogger;\n telemetry?: ThreeuTelemetryOptions;\n app?: ThreeuAppInfo;\n /** Extra headers merged into every request (never overrides auth/brand). */\n defaultHeaders?: Record<string, string>;\n /** Opt out of the dev-only unsafe-usage warnings. */\n suppressWarnings?: boolean;\n}\n\n/** Per-request overrides. */\nexport interface RequestOptions {\n query?: Record<string, unknown> | undefined;\n body?: unknown;\n headers?: Record<string, string>;\n timeoutMs?: number;\n retries?: number;\n signal?: AbortSignal;\n /** Idempotency key for safe retries of mutating requests. */\n idempotencyKey?: string;\n /** Mark the request idempotent so the client may auto-retry it. */\n idempotent?: boolean;\n /** Override the brand for this single request. */\n brand?: string | number;\n /** Raw multipart form data (skips JSON serialization). */\n form?: FormData;\n}\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\n/** A structured, safe-to-log SDK event. Never contains tokens/PII/raw bodies. */\nexport interface ThreeuLogEvent {\n type: ThreeuEventType;\n requestId?: string;\n method?: HttpMethod;\n /** Path template with ids elided, e.g. `/plugins/:id/webhook`. */\n pathTemplate?: string;\n resource?: string;\n action?: string;\n statusCode?: number;\n durationMs?: number;\n retryCount?: number;\n runtime?: ThreeuRuntime;\n sdkVersion?: string;\n appName?: string;\n developerId?: string | number;\n pluginId?: string | number;\n brandId?: string | number;\n errorCode?: string;\n errorType?: string;\n message?: string;\n}\n\nexport type ThreeuEventType =\n | \"request:start\"\n | \"request:success\"\n | \"request:error\"\n | \"request:retry\"\n | \"request:timeout\"\n | \"request:rate_limited\"\n | \"request:permission_denied\"\n | \"request:validation_failed\";\n\nexport interface ThreeuLogger {\n debug?(event: ThreeuLogEvent): void;\n info?(event: ThreeuLogEvent): void;\n warn?(event: ThreeuLogEvent): void;\n error?(event: ThreeuLogEvent): void;\n}\n","/**\n * Dev-time safety warnings. The SDK proactively warns about dangerous usage\n * (brief §\"detect obviously unsafe usage\"): secret tokens in a browser,\n * insecure base URLs in production, etc. Warnings are deduped and silent in\n * production unless a logger is wired.\n */\nimport { SECRET_TOKEN_TYPES, type TokenType } from \"./types\";\nimport { detectRuntime, isBrowserLike, isDevelopment, type ThreeuRuntime } from \"./runtime\";\n\nconst seen = new Set<string>();\n\n/** Emit a console warning at most once per unique message. */\nexport function warnOnce(key: string, message: string): void {\n if (seen.has(key)) return;\n seen.add(key);\n // eslint-disable-next-line no-console\n console.warn(`[@threeu/threeu] ${message}`);\n}\n\n/** Reset the dedup cache (used by tests). */\nexport function resetWarnings(): void {\n seen.clear();\n}\n\nexport interface SafetyContext {\n tokenType?: TokenType;\n baseUrl?: string;\n runtime?: ThreeuRuntime;\n suppressWarnings?: boolean;\n}\n\n/** Run the standard client-construction safety checks. */\nexport function runClientSafetyChecks(ctx: SafetyContext): void {\n if (ctx.suppressWarnings) return;\n const runtime = ctx.runtime ?? detectRuntime();\n\n if (ctx.tokenType && SECRET_TOKEN_TYPES.has(ctx.tokenType) && isBrowserLike(runtime)) {\n warnOnce(\n `secret-token-browser:${ctx.tokenType}`,\n `A \"${ctx.tokenType}\" token is being used in a browser-like runtime. Secret/admin/developer tokens must stay server-side — use a public storefront token in the browser instead.`,\n );\n }\n\n if (ctx.baseUrl && ctx.baseUrl.startsWith(\"http://\") && !isDevelopment()) {\n warnOnce(\n `insecure-base-url:${ctx.baseUrl}`,\n `Insecure base URL \"${ctx.baseUrl}\" in production. Use https://.`,\n );\n }\n}\n\n/** Warn when an admin-only operation is invoked from the browser. */\nexport function warnAdminInBrowser(operation: string, suppress?: boolean): void {\n if (suppress || !isBrowserLike()) return;\n warnOnce(\n `admin-op-browser:${operation}`,\n `Admin operation \"${operation}\" called from a browser runtime. This likely exposes privileged access to end users.`,\n );\n}\n"]}
|