@global-torque/invest-core 0.2.2

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 (75) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +11 -0
  4. package/README.md +76 -0
  5. package/SECURITY.md +9 -0
  6. package/SUPPORT.md +6 -0
  7. package/dist/node/app/config.js +173 -0
  8. package/dist/node/helpers/text.js +37 -0
  9. package/dist/node/markdown/tableWrap.js +23 -0
  10. package/package.json +113 -0
  11. package/src/accreditation/status.ts +100 -0
  12. package/src/analytics/__tests__/analyticsBody.test.ts +50 -0
  13. package/src/analytics/analyticsBody.ts +270 -0
  14. package/src/app/config.test.ts +79 -0
  15. package/src/app/config.ts +329 -0
  16. package/src/decimal/__tests__/canonicalDecimal.test.ts +58 -0
  17. package/src/decimal/canonicalDecimal.ts +154 -0
  18. package/src/evm/__tests__/walletInfo.test.ts +488 -0
  19. package/src/evm/walletInfo.ts +625 -0
  20. package/src/filer/__tests__/documentFormatter.test.ts +208 -0
  21. package/src/filer/__tests__/publicImage.test.ts +42 -0
  22. package/src/filer/documentFormatter.ts +195 -0
  23. package/src/filer/publicImage.ts +120 -0
  24. package/src/form-validation/__tests__/general.test.ts +78 -0
  25. package/src/form-validation/__tests__/investment.test.ts +100 -0
  26. package/src/form-validation/ajv.ts +109 -0
  27. package/src/form-validation/constants.ts +22 -0
  28. package/src/form-validation/general.ts +114 -0
  29. package/src/form-validation/index.ts +5 -0
  30. package/src/form-validation/investment.ts +65 -0
  31. package/src/form-validation/rules.ts +35 -0
  32. package/src/formatting/__tests__/buildInfo.test.ts +19 -0
  33. package/src/formatting/__tests__/dateTime.test.ts +24 -0
  34. package/src/formatting/__tests__/display.test.ts +30 -0
  35. package/src/formatting/buildInfo.ts +31 -0
  36. package/src/formatting/dateTime.ts +43 -0
  37. package/src/formatting/display.ts +24 -0
  38. package/src/helpers/arrays.ts +11 -0
  39. package/src/helpers/currency.ts +19 -0
  40. package/src/helpers/formatters/formatToDate.ts +47 -0
  41. package/src/helpers/formatters/formatToNumber.ts +39 -0
  42. package/src/helpers/formatters/formatToPhone.ts +13 -0
  43. package/src/helpers/general.ts +164 -0
  44. package/src/helpers/model.ts +87 -0
  45. package/src/helpers/numberFormatter.ts +4 -0
  46. package/src/helpers/text.ts +51 -0
  47. package/src/index.ts +22 -0
  48. package/src/investment/__tests__/status.test.ts +59 -0
  49. package/src/investment/rawAmount.test.ts +23 -0
  50. package/src/investment/rawAmount.ts +81 -0
  51. package/src/investment/status.ts +56 -0
  52. package/src/kyc/__tests__/kycAlert.formatter.test.ts +47 -0
  53. package/src/kyc/__tests__/kycAlert.test.ts +47 -0
  54. package/src/kyc/__tests__/thirdPartyScreen.test.ts +16 -0
  55. package/src/kyc/kycAlert.ts +47 -0
  56. package/src/kyc/status.ts +109 -0
  57. package/src/kyc/thirdPartyScreen.ts +28 -0
  58. package/src/markdown/tableWrap.ts +29 -0
  59. package/src/notifications/shareFields.ts +15 -0
  60. package/src/offer/__tests__/metrics.test.ts +67 -0
  61. package/src/offer/formatter.ts +559 -0
  62. package/src/offer/metrics.ts +83 -0
  63. package/src/onboarding/__tests__/intents.test.ts +83 -0
  64. package/src/onboarding/intents.ts +128 -0
  65. package/src/profiles/__tests__/formatting.test.ts +24 -0
  66. package/src/profiles/avatarInitial.ts +5 -0
  67. package/src/profiles/formatting.ts +38 -0
  68. package/src/repository/__tests__/formatterCache.test.ts +45 -0
  69. package/src/repository/formatterCache.ts +56 -0
  70. package/src/wallet/__tests__/auth.test.ts +102 -0
  71. package/src/wallet/__tests__/operationPresentation.test.ts +94 -0
  72. package/src/wallet/__tests__/setupError.test.ts +32 -0
  73. package/src/wallet/auth.ts +491 -0
  74. package/src/wallet/operationPresentation.ts +106 -0
  75. package/src/wallet/setupError.ts +50 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.2.2 candidate
4
+
5
+ - Reissued the framework candidate for fresh public-release validation; package APIs and runtime source remain unchanged.
6
+
7
+ ## 0.2.0 candidate
8
+
9
+ - Added exact Node conditional outputs for the three pure static configuration helpers.
10
+
11
+ - Created the standalone seven-package framework workspace.
12
+ - Moved investment validation policy and schema helpers into `invest-core`.
13
+ - Kept generic UI validation and URL synchronization at public UI Kit barrels.
14
+ - Removed company social destinations from framework-owned social metadata.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Global Torque
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,11 @@
1
+ # Notices
2
+
3
+ The framework package sources are transferred from
4
+ `global-torque/dashboard.webdevelop.biz` revision
5
+ `9f35aa0882b765e202538ce7c7d5e82eb1b452d3` under the MIT license. Third party
6
+ dependencies retain their own licenses and notices. This file records source
7
+ provenance only; it is not a blanket rights grant for host assets, fonts, or
8
+ brand material.
9
+
10
+ The checked Unovis patches are retained under `patches/` with their source
11
+ revision and SHA-256 values in `docs/source-reconciliation.md`.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @global-torque/invest-core
2
+
3
+ Pure investment-domain logic shared by apps and packages.
4
+
5
+ ## Ownership
6
+
7
+ This package owns deterministic helpers, mappers, formatters, calculations, and normalization utilities that do not need Vue, Pinia, router, browser app state, repositories, network clients, or runtime config singletons.
8
+
9
+ ## Allowed Dependencies
10
+
11
+ - `@global-torque/domain-types`.
12
+ - AJV, `ajv-errors`, `ajv-formats`, lodash, and markdown-it for the published
13
+ investment validation and formatting helpers.
14
+ - TypeScript and platform-standard value types.
15
+
16
+ ## Forbidden Dependencies
17
+
18
+ - Vue, Pinia, Vue Router, or app packages.
19
+ - The retired common compatibility layer, `@global-torque/ui-kit`, or `@global-torque/ui-primitives`.
20
+ - Network clients, repositories, stores, route views, `import.meta.env`, or runtime singleton config.
21
+
22
+ ## Public Exports
23
+
24
+ `app/config`, `markdown/tableWrap`, and `helpers/text` retain their source
25
+ TypeScript browser and type targets and additionally provide exact generated
26
+ `dist/node` JavaScript for plain Node/VitePress configuration consumers. The
27
+ candidate packer emits only these three Node files; all other exports remain
28
+ source based.
29
+
30
+ - `analytics/analyticsBody`: analytics request body normalization and redaction.
31
+ - `decimal/canonicalDecimal`: numeric(38,18) canonical-string validation, exact scaled-integer arithmetic, comparison, and display formatting.
32
+ - `evm/walletInfo`: EVM wallet status-only response normalization and deposit-address extraction.
33
+ - `filer/publicImage`: pure public filer image URL and `srcset` builders with injected `filerUrl`.
34
+ - `formatting/dateTime`: shared en-US date and local time formatting helpers.
35
+ - `formatting/display`: USD currency formatting and first-letter capitalization.
36
+ - `investment/status`: investment status and funding clickability helpers.
37
+ - `investment/rawAmount`: exact raw-token decimal conversion, comparison, and
38
+ presentation helpers that never use floating-point arithmetic.
39
+ - `kyc/status`, `kyc/kycAlert`, `kyc/thirdPartyScreen`: KYC status compatibility re-export from `domain-types` plus normalized view models.
40
+ - `offer/metrics`: offer funding percent, minimum investment, Reg D 506(c), recency, and funding-completion calculations.
41
+ - `profiles/formatting`: profile date and phone display formatters.
42
+ - `repository/formatterCache`: generic formatter memoization helper.
43
+ - `wallet/auth`, `wallet/setupError`: wallet-auth normalization and setup-required error classification.
44
+ - `wallet/operationPresentation`: wallet operation text shortening, asset/address labels, and injected explorer-link builders.
45
+ - `form-validation`: investment-only AJV policy, validation rule descriptors,
46
+ immutable schema composition, empty-string normalization, and pure schema
47
+ projection helpers. `createInvestmentAjv()` creates one validator per form;
48
+ legacy `file` and `maxFileSize` annotations are accepted as presentation
49
+ compatibility and do not change backend validation semantics.
50
+ - root export: compatibility barrel for the pure helpers above.
51
+
52
+ ## Example
53
+
54
+ ```ts
55
+ import { buildPublicFilerImageUrl } from '@global-torque/invest-core/filer/publicImage';
56
+
57
+ const imageUrl = buildPublicFilerImageUrl(42, 'medium', {
58
+ filerUrl: 'https://filer.example.com',
59
+ });
60
+ ```
61
+
62
+ Investment forms explicitly opt into policy at the UI boundary:
63
+
64
+ ```ts
65
+ import {
66
+ composeInvestmentFormSchema,
67
+ createInvestmentAjv,
68
+ prepareInvestmentFormData,
69
+ } from '@global-torque/invest-core/form-validation';
70
+
71
+ const validationOptions = {
72
+ createAjv: createInvestmentAjv,
73
+ composeSchema: composeInvestmentFormSchema,
74
+ prepareData: prepareInvestmentFormData,
75
+ };
76
+ ```
package/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Security
2
+
3
+ Please report security issues privately to the package owner before opening a
4
+ public issue. Do not include credentials, personal data, production URLs, or
5
+ real investment records in reports or fixtures.
6
+
7
+ This repository contains client-side framework code. Hosts remain responsible
8
+ for authentication, authorization, service origins, environment loading, and
9
+ provider configuration.
package/SUPPORT.md ADDED
@@ -0,0 +1,6 @@
1
+ # Support
2
+
3
+ Use the package README and public export list for integration questions. A
4
+ package source or type check does not prove that a host backend exposes the
5
+ same API revision. Report the host package, source revision, package version,
6
+ and exact failing public subpath with reproducible fixture data.
@@ -0,0 +1,173 @@
1
+ const REQUIRED_RUNTIME_BRAND_KEYS = [
2
+ 'profile', 'title', 'description', 'email', 'logo', 'logoReversed', 'mark', 'pwaName',
3
+ ];
4
+ const assertNonBlank = (value, path) => {
5
+ if (typeof value !== 'string' || value.trim() === '') {
6
+ throw new Error(`Static build configuration requires nonblank ${path}.`);
7
+ }
8
+ };
9
+ const assertObject = (value, path) => {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
11
+ throw new Error(`Static build configuration requires object ${path}.`);
12
+ }
13
+ return value;
14
+ };
15
+ const assertOptionalNonBlank = (value, path) => {
16
+ if (value !== undefined)
17
+ assertNonBlank(value, path);
18
+ };
19
+ export function assertInvestRuntimeBrandConfig(config, path = 'runtime') {
20
+ const runtime = assertObject(config, path);
21
+ const brand = assertObject(runtime.brand, `${path}.brand`);
22
+ for (const key of REQUIRED_RUNTIME_BRAND_KEYS) {
23
+ assertNonBlank(brand[key], `${path}.brand.${key}`);
24
+ }
25
+ const contact = assertObject(runtime.contact, `${path}.contact`);
26
+ for (const key of ['address1', 'address2', 'phone', 'email']) {
27
+ assertNonBlank(contact[key], `${path}.contact.${key}`);
28
+ }
29
+ const socials = assertObject(runtime.socials, `${path}.socials`);
30
+ if (Object.keys(socials).length === 0) {
31
+ throw new Error(`Static build configuration requires at least one ${path}.socials entry.`);
32
+ }
33
+ for (const [key, rawSocial] of Object.entries(socials)) {
34
+ const social = assertObject(rawSocial, `${path}.socials.${key}`);
35
+ for (const field of ['icon', 'iconName', 'name']) {
36
+ assertNonBlank(social[field], `${path}.socials.${key}.${field}`);
37
+ }
38
+ assertOptionalNonBlank(social.href, `${path}.socials.${key}.href`);
39
+ assertOptionalNonBlank(social.shareHref, `${path}.socials.${key}.shareHref`);
40
+ if (social.href === undefined && social.shareHref === undefined) {
41
+ throw new Error(`Static build configuration requires ${path}.socials.${key}.href or ${path}.socials.${key}.shareHref.`);
42
+ }
43
+ }
44
+ }
45
+ export function serializeStaticConfigForInlineScript(value) {
46
+ return JSON.stringify(value).replace(/</gu, '\\u003c');
47
+ }
48
+ const TURNKEY_SERVER_SIGN_PATH = 'auth/turnkey/server-sign';
49
+ const EVM_CONTRACT_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
50
+ const joinUrlPath = (baseUrl, path) => {
51
+ const trimmedBaseUrl = baseUrl.trim().replace(/\/+$/, '');
52
+ const trimmedPath = path.trim().replace(/^\/+/, '');
53
+ return trimmedBaseUrl && trimmedPath ? `${trimmedBaseUrl}/${trimmedPath}` : '';
54
+ };
55
+ const normalizeStableCoinAddress = (value) => {
56
+ const address = String(value ?? '').trim();
57
+ if (!address)
58
+ return '';
59
+ if (!EVM_CONTRACT_ADDRESS_PATTERN.test(address)) {
60
+ throw new Error('STABLE_COIN must be a 20-byte 0x-prefixed EVM contract address.');
61
+ }
62
+ return address.toLowerCase();
63
+ };
64
+ export function createInvestAppConfigFromEnv(env, staticConfig) {
65
+ const evmApiUrl = String(env.EVM_URL ?? '');
66
+ const turnkeyServerSignUrl = String(env.TURNKEY_SERVER_SIGN_URL ?? joinUrlPath(evmApiUrl, TURNKEY_SERVER_SIGN_PATH));
67
+ const baseConfig = {
68
+ env: String(env.ENV ?? env.MODE ?? ''),
69
+ isDev: env.DEV === true || env.DEV === 'true',
70
+ isStaticSite: env.IS_STATIC_SITE === true || env.IS_STATIC_SITE === 'true' || env.IS_STATIC_SITE === '1',
71
+ enableAnalytics: env.ENABLE_ANALYTICS === true || env.ENABLE_ANALYTICS === 'true' || env.ENABLE_ANALYTICS === '1',
72
+ cookieDomain: String(env.COOKIE_DOMAIN ?? ''),
73
+ stableCoinAddress: normalizeStableCoinAddress(env.STABLE_COIN),
74
+ urls: {
75
+ frontend: String(env.FRONTEND_URL ?? ''),
76
+ dashboard: String(env.FRONTEND_URL_DASHBOARD ?? ''),
77
+ static: String(env.FRONTEND_URL_STATIC ?? ''),
78
+ cryptoWalletScan: String(env.CRYPTO_WALLET_SCAN_URL ?? ''),
79
+ api: {
80
+ kratos: String(env.KRATOS_URL ?? ''),
81
+ plaid: String(env.PLAID_URL ?? ''),
82
+ investment: String(env.INVESTMENT_URL ?? ''),
83
+ offer: String(env.OFFER_URL ?? ''),
84
+ esign: String(env.ESIGN_URL ?? ''),
85
+ user: String(env.USER_URL ?? ''),
86
+ notification: String(env.NOTIFICATION_URL ?? ''),
87
+ accreditation: String(env.ACCREDITATION_URL ?? ''),
88
+ payments: String(env.PAYMENTS_URL ?? ''),
89
+ wallet: String(env.WALLET_URL ?? ''),
90
+ evm: evmApiUrl,
91
+ filer: String(env.FILER_URL ?? ''),
92
+ distributions: String(env.DISTRIBUTIONS_URL ?? ''),
93
+ analytic: String(env.ANALYTIC_URL ?? ''),
94
+ docuseal: String(env.DOCUSEAL_URL ?? ''),
95
+ fundManager: String(env.FUND_MANAGER_URL ?? ''),
96
+ },
97
+ },
98
+ demoAccount: {
99
+ email: String(env.DEMO_ACCOUNT_EMAIL ?? ''),
100
+ password: String(env.DEMO_ACCOUNT_PASSWORD ?? ''),
101
+ },
102
+ build: {
103
+ version: String(env.APP_VERSION ?? ''),
104
+ timestamp: String(env.APP_BUILD_TIMESTAMP ?? ''),
105
+ },
106
+ thirdParty: {
107
+ hellosignClientId: String(env.HELLOSIGN_CLIENT_ID ?? ''),
108
+ segmentKey: String(env.SEGMENT_KEY ?? ''),
109
+ alchemyWalletApiKey: String(env.ALCHEMY_WALLET_API_KEY ?? ''),
110
+ alchemy7702PolicyId: String(env.ALCHEMY_7702_POLICY_ID ?? ''),
111
+ turnkeyApiBaseUrl: String(env.TURNKEY_API_BASE_URL ?? 'https://api.turnkey.com'),
112
+ turnkeyOrganizationId: String(env.TURNKEY_ORGANIZATION_ID ?? ''),
113
+ turnkeyServerSignUrl,
114
+ turnkeySessionExpirationSeconds: String(env.TURNKEY_SESSION_EXPIRATION_SECONDS ?? '3600'),
115
+ turnkeyRegistrationEnabled: String(env.TURNKEY_REGISTRATION_ENABLED ?? 'false'),
116
+ },
117
+ };
118
+ if (!staticConfig)
119
+ return baseConfig;
120
+ return { ...baseConfig, brand: { ...staticConfig.brand } };
121
+ }
122
+ const trimTrailingSlash = (value) => String(value ?? '').replace(/\/$/, '');
123
+ export const createInvestAppLinks = (config) => {
124
+ const staticUrl = trimTrailingSlash(config.static);
125
+ const dashboardUrl = trimTrailingSlash(config.dashboard);
126
+ const profileTab = (profileId, tab) => (`${dashboardUrl}/profile/${profileId}/account?tab=${tab}`);
127
+ return {
128
+ home: `${staticUrl}/`,
129
+ signin: `${staticUrl}/signin`,
130
+ authenticator: `${staticUrl}/authenticator`,
131
+ signup: `${staticUrl}/signup`,
132
+ forgot: `${staticUrl}/forgot`,
133
+ resetPassword: `${dashboardUrl}/reset-password`,
134
+ checkEmail: `${staticUrl}/check-email`,
135
+ contactUs: `${staticUrl}/contact-us`,
136
+ offers: `${staticUrl}/offers`,
137
+ howItWorks: `${staticUrl}/how-it-works`,
138
+ faq: `${staticUrl}/faq`,
139
+ blog: `${staticUrl}/resource-center`,
140
+ terms: `${staticUrl}/legal/terms-of-use`,
141
+ privacy: `${staticUrl}/legal/privacy-policy`,
142
+ cookie: `${staticUrl}/legal/cookie`,
143
+ serverError: `${dashboardUrl}/500`,
144
+ notifications: `${dashboardUrl}/notifications`,
145
+ settings: (profileId) => `${dashboardUrl}/settings/${profileId}/mfa`,
146
+ settingsAccountDetails: (profileId) => `${dashboardUrl}/settings/${profileId}/account-details`,
147
+ settingsMfa: (profileId) => `${dashboardUrl}/settings/${profileId}/mfa`,
148
+ settingsSecurity: (profileId) => `${dashboardUrl}/settings/${profileId}/security`,
149
+ settingsBankAccounts: (profileId) => `${dashboardUrl}/settings/${profileId}/bank-accounts`,
150
+ profileAccreditation: (profileId) => `${dashboardUrl}/profile/${profileId}/accreditation`,
151
+ investmentTimeline: (profileId, investId) => (`${dashboardUrl}/profile/${profileId}/investment/${investId}/timeline`),
152
+ profileCryptoWallet: (profileId) => `${dashboardUrl}/profile/${profileId}/evmwallet`,
153
+ profileWallet: (profileId) => `${dashboardUrl}/profile/${profileId}/wallet`,
154
+ profileAccount: (profileId) => `${dashboardUrl}/profile/${profileId}/account`,
155
+ profileEarn: (profileId) => `${dashboardUrl}/profile/${profileId}/earn`,
156
+ earnOverview: (profileId, poolId) => (`${dashboardUrl}/profile/${profileId}/earn/${poolId}/overview`),
157
+ earnYourPosition: (profileId, poolId) => (`${dashboardUrl}/profile/${profileId}/earn/${poolId}/your-position`),
158
+ earnRisk: (profileId, poolId) => (`${dashboardUrl}/profile/${profileId}/earn/${poolId}/risk`),
159
+ profileKyc: (profileId) => `${dashboardUrl}/profile/${profileId}/kyc`,
160
+ profileWalletOtp: (profileId) => `${dashboardUrl}/profile/${profileId}/wallet-otp`,
161
+ profilePortfolio: (profileId) => `${dashboardUrl}/profile/${profileId}/portfolio`,
162
+ profileSummary: (profileId) => `${dashboardUrl}/profile/${profileId}/summary`,
163
+ profile: () => `${dashboardUrl}/profile`,
164
+ createProfile: () => `${dashboardUrl}/profile/create-new-profile`,
165
+ offerSingle: (slug) => `${staticUrl}/${slug}`,
166
+ blogSingle: (slug) => `${staticUrl}/resource-center/${slug}`,
167
+ profileTabSummary: (profileId) => profileTab(profileId, 'summary'),
168
+ profileTabPortfolio: (profileId) => profileTab(profileId, 'portfolio'),
169
+ profileTabWallet: (profileId) => profileTab(profileId, 'wallet'),
170
+ profileTabDistributions: (profileId) => profileTab(profileId, 'distributions'),
171
+ profileTabEarn: (profileId) => profileTab(profileId, 'earn'),
172
+ };
173
+ };
@@ -0,0 +1,37 @@
1
+ export const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2
+ export const stripMarkdown = (markdown) => {
3
+ // Replace bold text with plain text
4
+ markdown = markdown.replace(/\*\*(.+?)\*\*/g, '');
5
+ markdown = markdown.replace(/__(.+?)__/g, '');
6
+ // Replace italicized text with plain text
7
+ markdown = markdown.replace(/_(.+?)_/g, '');
8
+ markdown = markdown.replace(/\*(.+?)\*/g, '');
9
+ // Replace strikethrough text with plain text
10
+ markdown = markdown.replace(/~~(.+?)~~/g, '');
11
+ // Replace inline code blocks with plain text
12
+ markdown = markdown.replace(/`(.+?)`/g, '');
13
+ // Replace code blocks with plain text (multiline)
14
+ markdown = markdown.replace(/```[\s\S]*?```/g, '');
15
+ // Keep the text inside links but remove the URL
16
+ markdown = markdown.replace(/\[(.+?)\]\((.+?)\)/g, '$1');
17
+ // Remove images
18
+ markdown = markdown.replace(/!\[(.+?)\]\((.+?)\)/g, '');
19
+ // Remove headings
20
+ markdown = markdown.replace(/^#+\s+(.+?)\s*$/gm, '');
21
+ markdown = markdown.replace(/^\s*=+\s*$/gm, '');
22
+ markdown = markdown.replace(/^\s*-+\s*$/gm, '');
23
+ // Remove blockquotes
24
+ markdown = markdown.replace(/^\s*>\s+(.+?)\s*$/gm, '');
25
+ // Remove lists
26
+ markdown = markdown.replace(/^\s*[*+-]\s+(.+?)\s*$/gm, '');
27
+ markdown = markdown.replace(/^\s*\d+\.\s+(.+?)\s*$/gm, '');
28
+ // Remove horizontal lines
29
+ markdown = markdown.replace(/^\s*[-*_]{3,}\s*$/gm, '');
30
+ return markdown;
31
+ };
32
+ export function stripHtml(html) {
33
+ return html.replace(/(<([^>]+)>)/gi, '');
34
+ }
35
+ export function stripHtmlAndMarkdown(html) {
36
+ return stripMarkdown(stripHtml(html));
37
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Plugin to wrap tables in a div with overflow scrolling
3
+ * This allows tables to be horizontally scrollable on smaller screens
4
+ * Matches the VTable component's wrapper structure
5
+ */
6
+ export const tableWrap = (md) => {
7
+ // Store the original table renderers if they exist
8
+ const defaultTableOpen = md.renderer.rules.table_open || ((tokens, idx, options, env, self) => {
9
+ return self.renderToken(tokens, idx, options);
10
+ });
11
+ const defaultTableClose = md.renderer.rules.table_close || ((tokens, idx, options, env, self) => {
12
+ return self.renderToken(tokens, idx, options);
13
+ });
14
+ // Override table_open to add opening wrapper div
15
+ md.renderer.rules.table_open = (tokens, idx, options, env, self) => {
16
+ return '<div class="v-table__wrap">\n' + defaultTableOpen(tokens, idx, options, env, self);
17
+ };
18
+ // Override table_close to add closing wrapper div
19
+ md.renderer.rules.table_close = (tokens, idx, options, env, self) => {
20
+ return defaultTableClose(tokens, idx, options, env, self) + '\n</div>';
21
+ };
22
+ };
23
+ export default tableWrap;
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@global-torque/invest-core",
3
+ "private": false,
4
+ "version": "0.2.2",
5
+ "description": "Pure investment policy, calculations, formatters, and schema helpers.",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.12.0"
9
+ },
10
+ "sideEffects": false,
11
+ "type": "module",
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./app/config": {
15
+ "types": "./src/app/config.ts",
16
+ "node": "./dist/node/app/config.js",
17
+ "default": "./src/app/config.ts"
18
+ },
19
+ "./analytics/analyticsBody": "./src/analytics/analyticsBody.ts",
20
+ "./accreditation/status": "./src/accreditation/status.ts",
21
+ "./decimal/canonicalDecimal": "./src/decimal/canonicalDecimal.ts",
22
+ "./evm/walletInfo": "./src/evm/walletInfo.ts",
23
+ "./formatting/dateTime": "./src/formatting/dateTime.ts",
24
+ "./formatting/buildInfo": "./src/formatting/buildInfo.ts",
25
+ "./formatting/display": "./src/formatting/display.ts",
26
+ "./filer/documentFormatter": "./src/filer/documentFormatter.ts",
27
+ "./filer/publicImage": "./src/filer/publicImage.ts",
28
+ "./investment/status": "./src/investment/status.ts",
29
+ "./investment/rawAmount": "./src/investment/rawAmount.ts",
30
+ "./kyc/kycAlert": "./src/kyc/kycAlert.ts",
31
+ "./kyc/status": "./src/kyc/status.ts",
32
+ "./kyc/thirdPartyScreen": "./src/kyc/thirdPartyScreen.ts",
33
+ "./offer/metrics": "./src/offer/metrics.ts",
34
+ "./offer/formatter": "./src/offer/formatter.ts",
35
+ "./onboarding/intents": "./src/onboarding/intents.ts",
36
+ "./notifications/shareFields": "./src/notifications/shareFields.ts",
37
+ "./profiles/formatting": "./src/profiles/formatting.ts",
38
+ "./profiles/avatarInitial": "./src/profiles/avatarInitial.ts",
39
+ "./repository/formatterCache": "./src/repository/formatterCache.ts",
40
+ "./wallet/auth": "./src/wallet/auth.ts",
41
+ "./wallet/operationPresentation": "./src/wallet/operationPresentation.ts",
42
+ "./wallet/setupError": "./src/wallet/setupError.ts",
43
+ "./helpers/general": "./src/helpers/general.ts",
44
+ "./markdown/tableWrap": {
45
+ "types": "./src/markdown/tableWrap.ts",
46
+ "node": "./dist/node/markdown/tableWrap.js",
47
+ "default": "./src/markdown/tableWrap.ts"
48
+ },
49
+ "./helpers/currency": "./src/helpers/currency.ts",
50
+ "./helpers/numberFormatter": "./src/helpers/numberFormatter.ts",
51
+ "./helpers/text": {
52
+ "types": "./src/helpers/text.ts",
53
+ "node": "./dist/node/helpers/text.js",
54
+ "default": "./src/helpers/text.ts"
55
+ },
56
+ "./helpers/arrays": "./src/helpers/arrays.ts",
57
+ "./helpers/model": "./src/helpers/model.ts",
58
+ "./helpers/formatters/formatToDate": "./src/helpers/formatters/formatToDate.ts",
59
+ "./helpers/formatters/formatToNumber": "./src/helpers/formatters/formatToNumber.ts",
60
+ "./helpers/formatters/formatToPhone": "./src/helpers/formatters/formatToPhone.ts",
61
+ "./form-validation": "./src/form-validation/index.ts",
62
+ "./form-validation/ajv": "./src/form-validation/ajv.ts",
63
+ "./form-validation/constants": "./src/form-validation/constants.ts",
64
+ "./form-validation/general": "./src/form-validation/general.ts",
65
+ "./form-validation/rules": "./src/form-validation/rules.ts",
66
+ "./form-validation/investment": "./src/form-validation/investment.ts"
67
+ },
68
+ "dependencies": {
69
+ "@types/lodash": "^4.17.24",
70
+ "@types/markdown-it": "^14.1.2",
71
+ "ajv": "^8.20.0",
72
+ "ajv-errors": "^3.0.0",
73
+ "ajv-formats": "^3.0.1",
74
+ "lodash": "^4.18.1",
75
+ "markdown-it": "^14.3.0",
76
+ "@global-torque/domain-types": "0.2.2"
77
+ },
78
+ "devDependencies": {
79
+ "typescript": "^6.0.2",
80
+ "vitest": "4.1.11"
81
+ },
82
+ "files": [
83
+ "src",
84
+ "dist/node/app/config.js",
85
+ "dist/node/markdown/tableWrap.js",
86
+ "dist/node/helpers/text.js",
87
+ "README.md",
88
+ "LICENSE",
89
+ "NOTICE.md",
90
+ "CHANGELOG.md",
91
+ "SECURITY.md",
92
+ "SUPPORT.md"
93
+ ],
94
+ "repository": {
95
+ "type": "git",
96
+ "url": "git+https://github.com/global-torque/torque-packages.git",
97
+ "directory": "packages/invest-core"
98
+ },
99
+ "homepage": "https://github.com/global-torque/torque-packages#readme",
100
+ "bugs": {
101
+ "url": "https://github.com/global-torque/torque-packages/issues"
102
+ },
103
+ "publishConfig": {
104
+ "access": "public",
105
+ "provenance": true,
106
+ "tag": "latest"
107
+ },
108
+ "scripts": {
109
+ "typecheck": "tsc --noEmit -p tsconfig.json",
110
+ "test:run": "vitest run",
111
+ "build:node": "tsc -p tsconfig.node.json"
112
+ }
113
+ }
@@ -0,0 +1,100 @@
1
+ import {
2
+ AccreditationTypes,
3
+ type AccreditationTypes as AccreditationStatus,
4
+ } from '@global-torque/domain-types/accreditationTypes';
5
+
6
+ export {
7
+ AccreditationTypes,
8
+ type IAccreditation,
9
+ type IAccreditationData,
10
+ } from '@global-torque/domain-types/accreditationTypes';
11
+
12
+ export interface ITextStatuses {
13
+ text: string;
14
+ class: string;
15
+ button?: boolean;
16
+ tooltip?: string;
17
+ mobileText?: string;
18
+ }
19
+
20
+ export interface IAlerts {
21
+ title: string;
22
+ class: string;
23
+ button?: boolean;
24
+ tooltip?: string;
25
+ description?: string;
26
+ }
27
+
28
+ export const AccreditationTextStatuses: Record<AccreditationStatus, ITextStatuses> = {
29
+ [AccreditationTypes.new]: {
30
+ text: 'Verify Accreditation',
31
+ mobileText: 'Verify',
32
+ class: 'new',
33
+ button: true,
34
+ },
35
+ [AccreditationTypes.pending]: {
36
+ text: 'Verification In Progress',
37
+ mobileText: 'In Progress',
38
+ class: 'pending',
39
+ tooltip: 'Please wait while our legal team will review your documents. The process may take 2-4 days. '
40
+ + 'We will notify you automatically once we have more information.',
41
+ },
42
+ [AccreditationTypes.info_required]: {
43
+ text: 'Info required',
44
+ mobileText: 'Need Info',
45
+ class: 'info-required',
46
+ button: true,
47
+ },
48
+ [AccreditationTypes.expired]: {
49
+ text: 'Expired',
50
+ class: 'expired',
51
+ button: true,
52
+ },
53
+ [AccreditationTypes.approved]: {
54
+ text: 'Verified',
55
+ class: 'success',
56
+ },
57
+ [AccreditationTypes.declined]: {
58
+ text: 'Failed',
59
+ class: 'failed',
60
+ button: true,
61
+ },
62
+ };
63
+
64
+ export const AccreditationAlerts: Record<AccreditationStatus, IAlerts> = {
65
+ [AccreditationTypes.new]: {
66
+ title: 'Verify Accreditation',
67
+ description: 'Complete your accreditation verification for this profile.',
68
+ class: 'new',
69
+ button: true,
70
+ },
71
+ [AccreditationTypes.pending]: {
72
+ title: 'Verification In Progress',
73
+ description: 'Please wait while our legal team will review your documents. The process may take 2-4 days. '
74
+ + 'We will notify you automatically once we have more information.',
75
+ class: 'pending',
76
+ },
77
+ [AccreditationTypes.info_required]: {
78
+ title: 'Info required',
79
+ description: 'Please provide the additional accreditation information requested by our verification team.',
80
+ class: 'info-required',
81
+ button: true,
82
+ },
83
+ [AccreditationTypes.expired]: {
84
+ title: 'Expired',
85
+ description: 'Your accreditation has expired. Please upload updated documents to renew verification for this profile.',
86
+ class: 'expired',
87
+ button: true,
88
+ },
89
+ [AccreditationTypes.approved]: {
90
+ title: 'Verified',
91
+ description: 'Your accreditation has been approved and is active for this investment profile.',
92
+ class: 'success',
93
+ },
94
+ [AccreditationTypes.declined]: {
95
+ title: 'Failed',
96
+ description: 'Your accreditation could not be verified. Please resubmit your documents to continue.',
97
+ class: 'failed',
98
+ button: true,
99
+ },
100
+ };
@@ -0,0 +1,50 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ } from 'vitest';
6
+ import {
7
+ normalizeAnalyticsBody,
8
+ normalizeAnalyticsBodyForMethod,
9
+ } from '../analyticsBody';
10
+
11
+ describe('analyticsBody', () => {
12
+ it('redacts sensitive fields in object payloads', () => {
13
+ expect(normalizeAnalyticsBody({
14
+ email: 'user@example.com',
15
+ password: 'secret',
16
+ code: '123456',
17
+ nested: {
18
+ routing_number: '021000021',
19
+ note: 'Contact user@example.com before submitting',
20
+ tokenNote: 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwMTIzNDU2Nzg5MCJ9.signaturepart',
21
+ keep: true,
22
+ },
23
+ })).toEqual({
24
+ email: '[redacted]',
25
+ password: '[redacted]',
26
+ code: '[redacted]',
27
+ nested: {
28
+ routing_number: '[redacted]',
29
+ note: 'Contact [redacted] before submitting',
30
+ tokenNote: '[redacted]',
31
+ keep: true,
32
+ },
33
+ });
34
+ });
35
+
36
+ it('normalizes JSON strings and ignores invalid string bodies', () => {
37
+ expect(normalizeAnalyticsBody('{"first_name":"Jamie","keep":1}')).toEqual({
38
+ first_name: '[redacted]',
39
+ keep: 1,
40
+ });
41
+ expect(normalizeAnalyticsBody('not-json')).toEqual({});
42
+ });
43
+
44
+ it('only includes bodies for mutation methods', () => {
45
+ expect(normalizeAnalyticsBodyForMethod('GET', { keep: true })).toEqual({});
46
+ expect(normalizeAnalyticsBodyForMethod('POST', { keep: true })).toEqual({ redacted: true });
47
+ expect(normalizeAnalyticsBodyForMethod('POST', undefined)).toEqual({});
48
+ expect(normalizeAnalyticsBodyForMethod('POST', '')).toEqual({});
49
+ });
50
+ });