@laconius/core 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.
Files changed (90) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +31 -0
  3. package/lib/module/auth.js +182 -0
  4. package/lib/module/auth.js.map +1 -0
  5. package/lib/module/client.js +99 -0
  6. package/lib/module/client.js.map +1 -0
  7. package/lib/module/config.js +72 -0
  8. package/lib/module/config.js.map +1 -0
  9. package/lib/module/converters.js +31 -0
  10. package/lib/module/converters.js.map +1 -0
  11. package/lib/module/create-config.js +36 -0
  12. package/lib/module/create-config.js.map +1 -0
  13. package/lib/module/endpoints.js +89 -0
  14. package/lib/module/endpoints.js.map +1 -0
  15. package/lib/module/errors.js +76 -0
  16. package/lib/module/errors.js.map +1 -0
  17. package/lib/module/i18n.js +84 -0
  18. package/lib/module/i18n.js.map +1 -0
  19. package/lib/module/index.js +18 -0
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/media.js +182 -0
  22. package/lib/module/media.js.map +1 -0
  23. package/lib/module/models.js +2 -0
  24. package/lib/module/models.js.map +1 -0
  25. package/lib/module/package.json +1 -0
  26. package/lib/module/provider.js +182 -0
  27. package/lib/module/provider.js.map +1 -0
  28. package/lib/module/runtime.js +43 -0
  29. package/lib/module/runtime.js.map +1 -0
  30. package/lib/module/single-flight.js +23 -0
  31. package/lib/module/single-flight.js.map +1 -0
  32. package/lib/module/site-context.js +74 -0
  33. package/lib/module/site-context.js.map +1 -0
  34. package/lib/module/stores.js +66 -0
  35. package/lib/module/stores.js.map +1 -0
  36. package/lib/module/translations.js +42 -0
  37. package/lib/module/translations.js.map +1 -0
  38. package/lib/typescript/package.json +1 -0
  39. package/lib/typescript/src/auth.d.ts +31 -0
  40. package/lib/typescript/src/auth.d.ts.map +1 -0
  41. package/lib/typescript/src/client.d.ts +31 -0
  42. package/lib/typescript/src/client.d.ts.map +1 -0
  43. package/lib/typescript/src/config.d.ts +150 -0
  44. package/lib/typescript/src/config.d.ts.map +1 -0
  45. package/lib/typescript/src/converters.d.ts +17 -0
  46. package/lib/typescript/src/converters.d.ts.map +1 -0
  47. package/lib/typescript/src/create-config.d.ts +11 -0
  48. package/lib/typescript/src/create-config.d.ts.map +1 -0
  49. package/lib/typescript/src/endpoints.d.ts +36 -0
  50. package/lib/typescript/src/endpoints.d.ts.map +1 -0
  51. package/lib/typescript/src/errors.d.ts +43 -0
  52. package/lib/typescript/src/errors.d.ts.map +1 -0
  53. package/lib/typescript/src/i18n.d.ts +20 -0
  54. package/lib/typescript/src/i18n.d.ts.map +1 -0
  55. package/lib/typescript/src/index.d.ts +17 -0
  56. package/lib/typescript/src/index.d.ts.map +1 -0
  57. package/lib/typescript/src/media.d.ts +81 -0
  58. package/lib/typescript/src/media.d.ts.map +1 -0
  59. package/lib/typescript/src/models.d.ts +327 -0
  60. package/lib/typescript/src/models.d.ts.map +1 -0
  61. package/lib/typescript/src/provider.d.ts +37 -0
  62. package/lib/typescript/src/provider.d.ts.map +1 -0
  63. package/lib/typescript/src/runtime.d.ts +26 -0
  64. package/lib/typescript/src/runtime.d.ts.map +1 -0
  65. package/lib/typescript/src/single-flight.d.ts +7 -0
  66. package/lib/typescript/src/single-flight.d.ts.map +1 -0
  67. package/lib/typescript/src/site-context.d.ts +28 -0
  68. package/lib/typescript/src/site-context.d.ts.map +1 -0
  69. package/lib/typescript/src/stores.d.ts +54 -0
  70. package/lib/typescript/src/stores.d.ts.map +1 -0
  71. package/lib/typescript/src/translations.d.ts +40 -0
  72. package/lib/typescript/src/translations.d.ts.map +1 -0
  73. package/package.json +77 -0
  74. package/src/auth.ts +223 -0
  75. package/src/client.ts +133 -0
  76. package/src/config.ts +191 -0
  77. package/src/converters.ts +47 -0
  78. package/src/create-config.ts +29 -0
  79. package/src/endpoints.ts +133 -0
  80. package/src/errors.ts +104 -0
  81. package/src/i18n.ts +108 -0
  82. package/src/index.ts +123 -0
  83. package/src/media.ts +209 -0
  84. package/src/models.ts +384 -0
  85. package/src/provider.tsx +229 -0
  86. package/src/runtime.ts +57 -0
  87. package/src/single-flight.ts +23 -0
  88. package/src/site-context.ts +77 -0
  89. package/src/stores.ts +94 -0
  90. package/src/translations.ts +40 -0
@@ -0,0 +1,77 @@
1
+ import { queryOptions, useQuery } from '@tanstack/react-query';
2
+
3
+ import type { LaconiusRuntime } from './runtime';
4
+ import { getRuntime, laconiusQueryKey } from './runtime';
5
+ import type { BaseSite, Currency, Language } from './models';
6
+
7
+ /**
8
+ * Adapters are plain objects of functions, so overriding one call is a spread and replacing the
9
+ * whole object is the route to a non-OCC backend.
10
+ */
11
+ export type SiteContextAdapter = {
12
+ loadBaseSites(runtime: LaconiusRuntime): Promise<BaseSite[]>;
13
+ loadLanguages(runtime: LaconiusRuntime): Promise<Language[]>;
14
+ loadCurrencies(runtime: LaconiusRuntime): Promise<Currency[]>;
15
+ };
16
+
17
+ type ListResponse<T> = { baseSites?: T[]; languages?: T[]; currencies?: T[] };
18
+
19
+ export const defaultSiteContextAdapter: SiteContextAdapter = {
20
+ async loadBaseSites({ client, converters }) {
21
+ const response = await client.request<ListResponse<unknown>>({
22
+ endpoint: 'baseSites',
23
+ });
24
+ return converters.convertAll<unknown, BaseSite>(response?.baseSites, 'baseSite');
25
+ },
26
+ async loadLanguages({ client, converters }) {
27
+ const response = await client.request<ListResponse<unknown>>({
28
+ endpoint: 'languages',
29
+ });
30
+ return converters.convertAll<unknown, Language>(response?.languages, 'language');
31
+ },
32
+ async loadCurrencies({ client, converters }) {
33
+ const response = await client.request<ListResponse<unknown>>({
34
+ endpoint: 'currencies',
35
+ });
36
+ return converters.convertAll<unknown, Currency>(response?.currencies, 'currency');
37
+ },
38
+ };
39
+
40
+ function adapter(runtime: LaconiusRuntime): SiteContextAdapter {
41
+ return {
42
+ ...defaultSiteContextAdapter,
43
+ ...runtime.config.backend.adapters?.siteContext,
44
+ };
45
+ }
46
+
47
+ /** `queryOptions` factories, for `prefetchQuery` / `useSuspenseQuery` without forking Laconius. */
48
+ export const siteContextQueries = {
49
+ baseSites: () =>
50
+ queryOptions({
51
+ queryKey: laconiusQueryKey('siteContext', 'baseSites'),
52
+ queryFn: () => {
53
+ const runtime = getRuntime();
54
+ return adapter(runtime).loadBaseSites(runtime);
55
+ },
56
+ }),
57
+ languages: () =>
58
+ queryOptions({
59
+ queryKey: laconiusQueryKey('siteContext', 'languages'),
60
+ queryFn: () => {
61
+ const runtime = getRuntime();
62
+ return adapter(runtime).loadLanguages(runtime);
63
+ },
64
+ }),
65
+ currencies: () =>
66
+ queryOptions({
67
+ queryKey: laconiusQueryKey('siteContext', 'currencies'),
68
+ queryFn: () => {
69
+ const runtime = getRuntime();
70
+ return adapter(runtime).loadCurrencies(runtime);
71
+ },
72
+ }),
73
+ };
74
+
75
+ export const useBaseSites = () => useQuery(siteContextQueries.baseSites());
76
+ export const useLanguages = () => useQuery(siteContextQueries.languages());
77
+ export const useCurrencies = () => useQuery(siteContextQueries.currencies());
package/src/stores.ts ADDED
@@ -0,0 +1,94 @@
1
+ import AsyncStorage from 'expo-sqlite/kv-store';
2
+ import { create } from 'zustand';
3
+ import { createJSONStorage, persist } from 'zustand/middleware';
4
+
5
+ export type SessionState = {
6
+ /** Memory only — never persisted. */
7
+ accessToken?: string;
8
+ /** Mirror of the value in secure storage, kept for the refresh call. */
9
+ refreshToken?: string;
10
+ /** Epoch milliseconds. */
11
+ expiresAt?: number;
12
+ setSession: (
13
+ session: Pick<SessionState, 'accessToken' | 'refreshToken' | 'expiresAt'>,
14
+ ) => void;
15
+ clearSession: () => void;
16
+ };
17
+
18
+ /** Not persisted: [ADR-0003](docs/adr/0003-refresh-token-in-the-keychain.md). */
19
+ export const useSessionStore = create<SessionState>((set) => ({
20
+ setSession: ({ accessToken, refreshToken, expiresAt }) =>
21
+ set({ accessToken, refreshToken, expiresAt }),
22
+ clearSession: () =>
23
+ set({
24
+ accessToken: undefined,
25
+ refreshToken: undefined,
26
+ expiresAt: undefined,
27
+ }),
28
+ }));
29
+
30
+ /** Derived, never stored. Spartacus needs a `UserIdService`; Zustand does not. */
31
+ export function selectUserId(state: {
32
+ accessToken?: string;
33
+ }): 'current' | 'anonymous' {
34
+ return state.accessToken ? 'current' : 'anonymous';
35
+ }
36
+
37
+ export type SiteContextState = {
38
+ language?: string;
39
+ currency?: string;
40
+ setLanguage: (language: string) => void;
41
+ setCurrency: (currency: string) => void;
42
+ };
43
+
44
+ /**
45
+ * Base site is immutable config; language and currency are runtime state. Both are part of every
46
+ * query key and drive translation lookup, so a switch retranslates and refetches in one move.
47
+ */
48
+ export const useSiteContextStore = create<SiteContextState>()(
49
+ persist(
50
+ (set) => ({
51
+ setLanguage: (language) => set({ language }),
52
+ setCurrency: (currency) => set({ currency }),
53
+ }),
54
+ {
55
+ name: 'laconius.siteContext',
56
+ storage: createJSONStorage(() => AsyncStorage),
57
+ partialize: (state) => ({
58
+ language: state.language,
59
+ currency: state.currency,
60
+ }),
61
+ },
62
+ ),
63
+ );
64
+
65
+ /**
66
+ * A persisted value outlives the config that produced it: drop the base store's last currency and
67
+ * the app restores it on every cold start, and every OCC call answers `UnsupportedCurrencyError`
68
+ * with no way back. So a stored value counts only while the config still offers it.
69
+ */
70
+ function stored(value: string | undefined, allowed: string[]): string | undefined {
71
+ return value !== undefined && allowed.includes(value) ? value : undefined;
72
+ }
73
+
74
+ /** Active language, falling back to config: the store starts empty until the app picks one. */
75
+ export function resolveLanguage(config: {
76
+ context: { languages: string[]; defaultLanguage?: string };
77
+ }): string | undefined {
78
+ return (
79
+ stored(useSiteContextStore.getState().language, config.context.languages) ??
80
+ config.context.defaultLanguage ??
81
+ config.context.languages[0]
82
+ );
83
+ }
84
+
85
+ /** Active currency, falling back to config. */
86
+ export function resolveCurrency(config: {
87
+ context: { currencies: string[]; defaultCurrency?: string };
88
+ }): string | undefined {
89
+ return (
90
+ stored(useSiteContextStore.getState().currency, config.context.currencies) ??
91
+ config.context.defaultCurrency ??
92
+ config.context.currencies[0]
93
+ );
94
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * English error strings, ported from Spartacus'
3
+ * `core-libs/assets/src/translations/en/common.json` (`httpHandlers.*`), minus the checkout and
4
+ * payment validation keys. Other languages are the app's: Spartacus ships 17 under
5
+ * `core-libs/assets/src/translations/<lang>/` and is Apache-2.0.
6
+ */
7
+ export const defaultCoreTranslations = {
8
+ en: {
9
+ common: {
10
+ retry: 'Retry',
11
+ cancel: 'Cancel',
12
+ close: 'Close',
13
+ loading: 'Loading…',
14
+ },
15
+ errors: {
16
+ http: {
17
+ badRequest: 'The request could not be processed. Please check your input.',
18
+ unauthorized: 'Your session has expired. Please login again.',
19
+ forbidden:
20
+ 'You are not authorized to perform this action. Please contact your administrator if you think this is a mistake.',
21
+ notFound: 'Item not found.',
22
+ conflict: 'Already exists.',
23
+ serverError: 'A server error occurred. Please try again later.',
24
+ offline: 'No connection. Please check your network and try again.',
25
+ unknown: 'An unknown error occurred.',
26
+ },
27
+ occ: {
28
+ InsufficientStockError: 'Not enough stock for this product.',
29
+ CartError: 'Cart errors occurred.',
30
+ CartEntryError: 'This cart entry could not be updated.',
31
+ UnknownIdentifierError: 'Item not found.',
32
+ CMSItemNotFoundError: 'This page is not available.',
33
+ ValidationError: 'Some of the information entered is not valid.',
34
+ DuplicateUidError: 'An account with this email address already exists.',
35
+ PasswordMismatchError: 'Old password incorrect.',
36
+ InvalidTokenError: 'Invalid code provided.',
37
+ },
38
+ },
39
+ },
40
+ };