@proteos/sdk 0.18.1
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/LICENSE +40 -0
- package/dist/chunk-7RGN4E22.cjs +1185 -0
- package/dist/chunk-7RGN4E22.cjs.map +1 -0
- package/dist/chunk-XJP5WCRZ.js +1125 -0
- package/dist/chunk-XJP5WCRZ.js.map +1 -0
- package/dist/index.cjs +2384 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5225 -0
- package/dist/index.d.ts +5225 -0
- package/dist/index.js +2146 -0
- package/dist/index.js.map +1 -0
- package/dist/meta/index.cjs +204 -0
- package/dist/meta/index.cjs.map +1 -0
- package/dist/meta/index.d.cts +2 -0
- package/dist/meta/index.d.ts +2 -0
- package/dist/meta/index.js +3 -0
- package/dist/meta/index.js.map +1 -0
- package/dist/types-BNsjfU8N.d.cts +3299 -0
- package/dist/types-BNsjfU8N.d.ts +3299 -0
- package/package.json +86 -0
- package/src/agent/agents.ts +53 -0
- package/src/agent/index.ts +134 -0
- package/src/agent/mcp-servers.ts +102 -0
- package/src/agent/prompts.ts +80 -0
- package/src/agent/session-types.ts +397 -0
- package/src/agent/sessions.ts +197 -0
- package/src/agent/skills.ts +89 -0
- package/src/agent/tools.ts +53 -0
- package/src/agent/types.ts +362 -0
- package/src/auth/index.ts +111 -0
- package/src/auth/me.ts +46 -0
- package/src/auth/organizations.ts +128 -0
- package/src/auth/platform-entities.ts +78 -0
- package/src/auth/roles.ts +213 -0
- package/src/auth/types.ts +294 -0
- package/src/auth/users.ts +226 -0
- package/src/client.ts +441 -0
- package/src/connector/index.ts +120 -0
- package/src/connector/types.ts +150 -0
- package/src/conversation/index.ts +297 -0
- package/src/conversation/types.ts +590 -0
- package/src/conversation/voice.ts +123 -0
- package/src/data/index.ts +53 -0
- package/src/data/queries.ts +66 -0
- package/src/data/records.ts +122 -0
- package/src/data/types.ts +89 -0
- package/src/errors.ts +148 -0
- package/src/events/index.ts +172 -0
- package/src/events/types.ts +77 -0
- package/src/functions/actions.ts +95 -0
- package/src/functions/index.ts +32 -0
- package/src/functions/types.ts +71 -0
- package/src/http/index.ts +2 -0
- package/src/http/query-params.ts +106 -0
- package/src/index.ts +598 -0
- package/src/iterator.ts +183 -0
- package/src/knowledge/graph.ts +35 -0
- package/src/knowledge/index.ts +104 -0
- package/src/knowledge/labels.ts +70 -0
- package/src/knowledge/links.ts +65 -0
- package/src/knowledge/nodes.ts +198 -0
- package/src/knowledge/record-links.ts +66 -0
- package/src/knowledge/types.ts +569 -0
- package/src/meta/apps.ts +107 -0
- package/src/meta/components.ts +124 -0
- package/src/meta/currency/index.ts +202 -0
- package/src/meta/entities.ts +193 -0
- package/src/meta/filters.ts +76 -0
- package/src/meta/index.ts +227 -0
- package/src/meta/layout/common-props.ts +93 -0
- package/src/meta/layout/control-registry.json +70 -0
- package/src/meta/layout/control-registry.ts +92 -0
- package/src/meta/layout/elements.ts +203 -0
- package/src/meta/layout/index.ts +41 -0
- package/src/meta/layout/page-layout.ts +35 -0
- package/src/meta/layout/size-value.ts +27 -0
- package/src/meta/list-views.ts +109 -0
- package/src/meta/lists.ts +104 -0
- package/src/meta/menu-configurations.ts +128 -0
- package/src/meta/modules.ts +159 -0
- package/src/meta/pages.ts +106 -0
- package/src/meta/types.ts +1115 -0
- package/src/meta/variables.ts +98 -0
- package/src/storage/files.ts +183 -0
- package/src/storage/index.ts +33 -0
- package/src/storage/types.ts +70 -0
- package/src/types/common.ts +143 -0
- package/src/types/index.ts +28 -0
- package/src/types/options.ts +95 -0
- package/src/workflow/executions.ts +99 -0
- package/src/workflow/index.ts +109 -0
- package/src/workflow/node-types.ts +50 -0
- package/src/workflow/types.ts +658 -0
- package/src/workflow/workflows.ts +152 -0
package/src/meta/apps.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ProteosClient } from '../client.js'
|
|
2
|
+
import { PageIterator } from '../iterator.js'
|
|
3
|
+
import type { ListResult } from '../types/common.js'
|
|
4
|
+
import type { App, CreateAppRequest, ListAppsOptions, UpdateAppRequest } from './types.js'
|
|
5
|
+
|
|
6
|
+
const APPS_BASE_PATH = '/meta/v1/apps'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service for managing apps.
|
|
10
|
+
* Apps group menu configurations and other org-scoped metadata under a
|
|
11
|
+
* stable slug. Slugs are unique per org.
|
|
12
|
+
*/
|
|
13
|
+
export interface AppService {
|
|
14
|
+
/**
|
|
15
|
+
* Lists apps with optional filtering.
|
|
16
|
+
*
|
|
17
|
+
* @param options - Filter and pagination options
|
|
18
|
+
* @returns Async iterator over apps
|
|
19
|
+
*/
|
|
20
|
+
list(options?: ListAppsOptions): PageIterator<App, ListAppsOptions>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fetches a single page of apps.
|
|
24
|
+
*/
|
|
25
|
+
listPage(options?: ListAppsOptions): Promise<ListResult<App>>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Gets a single app by slug.
|
|
29
|
+
*
|
|
30
|
+
* @param slug - App slug (unique within the caller's org)
|
|
31
|
+
* @returns The app
|
|
32
|
+
* @throws {ProteosError} If app not found (404)
|
|
33
|
+
*/
|
|
34
|
+
get(slug: string): Promise<App>
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new app.
|
|
38
|
+
*
|
|
39
|
+
* @param request - App creation request
|
|
40
|
+
* @returns The created app
|
|
41
|
+
* @throws {ProteosError} If validation fails (400) or conflict (409)
|
|
42
|
+
*/
|
|
43
|
+
create(request: CreateAppRequest): Promise<App>
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates or updates an app idempotently by slug. The URL slug wins on
|
|
47
|
+
* mismatch with the body's slug field (`PUT /meta/v1/apps/:slug`).
|
|
48
|
+
*
|
|
49
|
+
* @param slug - App slug
|
|
50
|
+
* @param request - Full app body
|
|
51
|
+
* @returns The created or updated app
|
|
52
|
+
*/
|
|
53
|
+
upsert(slug: string, request: CreateAppRequest): Promise<App>
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Updates an existing app.
|
|
57
|
+
*
|
|
58
|
+
* @param slug - App slug
|
|
59
|
+
* @param request - Fields to update
|
|
60
|
+
* @returns The updated app
|
|
61
|
+
* @throws {ProteosError} If app not found (404) or validation fails (400)
|
|
62
|
+
*/
|
|
63
|
+
update(slug: string, request: UpdateAppRequest): Promise<App>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Deletes an app.
|
|
67
|
+
*
|
|
68
|
+
* @param slug - App slug
|
|
69
|
+
* @throws {ProteosError} If app not found (404)
|
|
70
|
+
*/
|
|
71
|
+
delete(slug: string): Promise<void>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Implementation of AppService.
|
|
76
|
+
*/
|
|
77
|
+
export class AppServiceImpl implements AppService {
|
|
78
|
+
constructor(private readonly client: ProteosClient) {}
|
|
79
|
+
|
|
80
|
+
list(options: ListAppsOptions = {}): PageIterator<App, ListAppsOptions> {
|
|
81
|
+
return new PageIterator((opts) => this.listPage(opts), options)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async listPage(options: ListAppsOptions = {}): Promise<ListResult<App>> {
|
|
85
|
+
return this.client.requestWithQuery<ListResult<App>>('GET', APPS_BASE_PATH, options)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async get(slug: string): Promise<App> {
|
|
89
|
+
return this.client.request<App>('GET', `${APPS_BASE_PATH}/${slug}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async create(request: CreateAppRequest): Promise<App> {
|
|
93
|
+
return this.client.request<App>('POST', APPS_BASE_PATH, request)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async upsert(slug: string, request: CreateAppRequest): Promise<App> {
|
|
97
|
+
return this.client.request<App>('PUT', `${APPS_BASE_PATH}/${slug}`, { ...request, slug })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async update(slug: string, request: UpdateAppRequest): Promise<App> {
|
|
101
|
+
return this.client.request<App>('PATCH', `${APPS_BASE_PATH}/${slug}`, request)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async delete(slug: string): Promise<void> {
|
|
105
|
+
await this.client.request<void>('DELETE', `${APPS_BASE_PATH}/${slug}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ProteosClient } from '../client.js'
|
|
2
|
+
import { PageIterator } from '../iterator.js'
|
|
3
|
+
import type { ListResult } from '../types/common.js'
|
|
4
|
+
import type {
|
|
5
|
+
Component,
|
|
6
|
+
CreateComponentRequest,
|
|
7
|
+
ListComponentsOptions,
|
|
8
|
+
UpdateComponentRequest,
|
|
9
|
+
} from './types.js'
|
|
10
|
+
|
|
11
|
+
const COMPONENTS_BASE_PATH = '/meta/v1/components'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service for managing UI component definitions.
|
|
15
|
+
* Components define reusable UI elements in the system.
|
|
16
|
+
*/
|
|
17
|
+
export interface ComponentService {
|
|
18
|
+
/**
|
|
19
|
+
* Lists components with optional filtering.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Filter and pagination options
|
|
22
|
+
* @returns Async iterator over components
|
|
23
|
+
*/
|
|
24
|
+
list(options?: ListComponentsOptions): PageIterator<Component, ListComponentsOptions>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetches a single page of components.
|
|
28
|
+
*/
|
|
29
|
+
listPage(options?: ListComponentsOptions): Promise<ListResult<Component>>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets a single component by ID.
|
|
33
|
+
*
|
|
34
|
+
* @param id - Component ID
|
|
35
|
+
* @returns The component
|
|
36
|
+
* @throws {ProteosError} If component not found (404)
|
|
37
|
+
*/
|
|
38
|
+
get(id: string): Promise<Component>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new component.
|
|
42
|
+
*
|
|
43
|
+
* @param request - Component creation request
|
|
44
|
+
* @returns The created component
|
|
45
|
+
* @throws {ProteosError} If validation fails (400) or conflict (409)
|
|
46
|
+
*/
|
|
47
|
+
create(request: CreateComponentRequest): Promise<Component>
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates or updates a component (upsert operation).
|
|
51
|
+
* If a component with the same slug exists, it is updated; otherwise, a new one is created.
|
|
52
|
+
*
|
|
53
|
+
* @param request - Component creation request
|
|
54
|
+
* @returns The created or updated component
|
|
55
|
+
*/
|
|
56
|
+
upsert(request: CreateComponentRequest): Promise<Component>
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Updates an existing component.
|
|
60
|
+
*
|
|
61
|
+
* @param id - Component ID
|
|
62
|
+
* @param request - Fields to update
|
|
63
|
+
* @returns The updated component
|
|
64
|
+
* @throws {ProteosError} If component not found (404) or validation fails (400)
|
|
65
|
+
*/
|
|
66
|
+
update(id: string, request: UpdateComponentRequest): Promise<Component>
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Deletes a component.
|
|
70
|
+
*
|
|
71
|
+
* @param id - Component ID
|
|
72
|
+
* @throws {ProteosError} If component not found (404)
|
|
73
|
+
*/
|
|
74
|
+
delete(id: string): Promise<void>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns the URL the component runtime imports to load a component's
|
|
78
|
+
* compiled ESM bundle (`GET /meta/v1/components/:slug/bundle`). The path is
|
|
79
|
+
* gateway-prefixed and relative; prepend the API origin to fetch it.
|
|
80
|
+
*
|
|
81
|
+
* @param slug - Component slug
|
|
82
|
+
* @returns The relative bundle URL
|
|
83
|
+
*/
|
|
84
|
+
bundleUrl(slug: string): string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Implementation of ComponentService.
|
|
89
|
+
*/
|
|
90
|
+
export class ComponentServiceImpl implements ComponentService {
|
|
91
|
+
constructor(private readonly client: ProteosClient) {}
|
|
92
|
+
|
|
93
|
+
list(options: ListComponentsOptions = {}): PageIterator<Component, ListComponentsOptions> {
|
|
94
|
+
return new PageIterator((opts) => this.listPage(opts), options)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async listPage(options: ListComponentsOptions = {}): Promise<ListResult<Component>> {
|
|
98
|
+
return this.client.requestWithQuery<ListResult<Component>>('GET', COMPONENTS_BASE_PATH, options)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async get(id: string): Promise<Component> {
|
|
102
|
+
return this.client.request<Component>('GET', `${COMPONENTS_BASE_PATH}/${id}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async create(request: CreateComponentRequest): Promise<Component> {
|
|
106
|
+
return this.client.request<Component>('POST', COMPONENTS_BASE_PATH, request)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async upsert(request: CreateComponentRequest): Promise<Component> {
|
|
110
|
+
return this.client.request<Component>('POST', `${COMPONENTS_BASE_PATH}/upsert`, request)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async update(id: string, request: UpdateComponentRequest): Promise<Component> {
|
|
114
|
+
return this.client.request<Component>('PATCH', `${COMPONENTS_BASE_PATH}/${id}`, request)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async delete(id: string): Promise<void> {
|
|
118
|
+
await this.client.request<void>('DELETE', `${COMPONENTS_BASE_PATH}/${id}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
bundleUrl(slug: string): string {
|
|
122
|
+
return `${COMPONENTS_BASE_PATH}/${slug}/bundle`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { CurrencyValue } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Currency + locale-number helpers backed entirely by the runtime's built-in
|
|
5
|
+
* `Intl` data — no bundled ISO-4217 table, no codegen. The platform validates
|
|
6
|
+
* codes on the backend (golang.org/x/text/currency); this module sources the
|
|
7
|
+
* selectable list and renders codes/symbols/amounts for the UI.
|
|
8
|
+
*
|
|
9
|
+
* Amounts are canonical decimal STRINGS (`^-?\d+(\.\d+)?$`) end-to-end; every
|
|
10
|
+
* formatter here operates on the string and never round-trips through a JS
|
|
11
|
+
* number, so precision and trailing zeros are preserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
let cachedCodes: string[] | null = null
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* All currency codes the runtime knows, sorted and memoized. Sourced from
|
|
18
|
+
* `Intl.supportedValuesOf` (ES2022). When an attribute's
|
|
19
|
+
* `allowed_currency_codes` is unset, this is the full selectable set.
|
|
20
|
+
*/
|
|
21
|
+
export function allCurrencyCodes(): string[] {
|
|
22
|
+
if (cachedCodes) return cachedCodes
|
|
23
|
+
const supported =
|
|
24
|
+
typeof Intl !== 'undefined' && 'supportedValuesOf' in Intl
|
|
25
|
+
? (Intl as unknown as { supportedValuesOf(key: 'currency'): string[] }).supportedValuesOf(
|
|
26
|
+
'currency',
|
|
27
|
+
)
|
|
28
|
+
: []
|
|
29
|
+
cachedCodes = [...supported].sort()
|
|
30
|
+
return cachedCodes
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Localized display name for a currency code (e.g. `"US Dollar"`), via
|
|
35
|
+
* `Intl.DisplayNames`. Falls back to the code itself when unavailable.
|
|
36
|
+
*/
|
|
37
|
+
export function currencyLabel(code: string, locale?: string): string {
|
|
38
|
+
try {
|
|
39
|
+
const names = new Intl.DisplayNames(locale ? [locale] : undefined, { type: 'currency' })
|
|
40
|
+
return names.of(code) ?? code
|
|
41
|
+
} catch {
|
|
42
|
+
return code
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The narrow symbol for a currency code (e.g. `"$"`, `"€"`, `"¥"`), via
|
|
48
|
+
* `Intl.NumberFormat`'s `narrowSymbol` so it's the bare glyph rather than a
|
|
49
|
+
* disambiguated form like `"US$"`. Falls back to the code itself.
|
|
50
|
+
*/
|
|
51
|
+
export function currencySymbol(code: string, locale?: string): string {
|
|
52
|
+
try {
|
|
53
|
+
const parts = new Intl.NumberFormat(locale, {
|
|
54
|
+
style: 'currency',
|
|
55
|
+
currency: code,
|
|
56
|
+
currencyDisplay: 'narrowSymbol',
|
|
57
|
+
}).formatToParts(0)
|
|
58
|
+
return parts.find((part) => part.type === 'currency')?.value ?? code
|
|
59
|
+
} catch {
|
|
60
|
+
return code
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type CurrencySymbolSide = 'prefix' | 'suffix'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Currencies whose symbol conventionally TRAILS the amount (with a space) —
|
|
68
|
+
* e.g. `"1.234,56 €"`, `"100 kr"`, `"100 zł"`. Everything else prefixes the
|
|
69
|
+
* symbol with no space — e.g. `"$1,234.56"`, `"¥100"`.
|
|
70
|
+
*
|
|
71
|
+
* This is deliberately currency-driven, NOT locale-driven: `Intl` would place
|
|
72
|
+
* the symbol per the *viewer's* locale, but we want a dollar to always read
|
|
73
|
+
* `"$100"` and a euro `"100 €"` regardless of who's looking. Extend this set as
|
|
74
|
+
* needed — it's the single source of truth for symbol placement.
|
|
75
|
+
*/
|
|
76
|
+
const SUFFIX_SYMBOL_CURRENCIES = new Set<string>([
|
|
77
|
+
'EUR',
|
|
78
|
+
'PLN',
|
|
79
|
+
'CZK',
|
|
80
|
+
'HUF',
|
|
81
|
+
'RON',
|
|
82
|
+
'BGN',
|
|
83
|
+
'HRK',
|
|
84
|
+
'SEK',
|
|
85
|
+
'NOK',
|
|
86
|
+
'DKK',
|
|
87
|
+
'ISK',
|
|
88
|
+
'RSD',
|
|
89
|
+
'RUB',
|
|
90
|
+
'UAH',
|
|
91
|
+
'GEL',
|
|
92
|
+
'MKD',
|
|
93
|
+
'ALL',
|
|
94
|
+
'AMD',
|
|
95
|
+
'AZN',
|
|
96
|
+
'VND',
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Which side of the amount a currency's symbol sits on. The well-defined rule:
|
|
101
|
+
* a currency in {@link SUFFIX_SYMBOL_CURRENCIES} suffixes (`"100 €"`), every
|
|
102
|
+
* other currency prefixes (`"$100"`). Pure and deterministic.
|
|
103
|
+
*/
|
|
104
|
+
export function currencySymbolSide(code: string): CurrencySymbolSide {
|
|
105
|
+
return SUFFIX_SYMBOL_CURRENCIES.has(code) ? 'suffix' : 'prefix'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const separatorCache = new Map<string, { group: string; decimal: string }>()
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The grouping (thousands) and decimal separators for a locale — e.g. `de-DE`
|
|
112
|
+
* → `{ group: ".", decimal: "," }`, `en-US` → `{ group: ",", decimal: "." }`.
|
|
113
|
+
* Derived from `Intl.NumberFormat.formatToParts` and memoized per locale.
|
|
114
|
+
*/
|
|
115
|
+
export function localeNumberSeparators(locale?: string): { group: string; decimal: string } {
|
|
116
|
+
const key = locale ?? '__default__'
|
|
117
|
+
const cached = separatorCache.get(key)
|
|
118
|
+
if (cached) return cached
|
|
119
|
+
let group = ','
|
|
120
|
+
let decimal = '.'
|
|
121
|
+
try {
|
|
122
|
+
const parts = new Intl.NumberFormat(locale).formatToParts(11111.1)
|
|
123
|
+
group = parts.find((part) => part.type === 'group')?.value ?? group
|
|
124
|
+
decimal = parts.find((part) => part.type === 'decimal')?.value ?? decimal
|
|
125
|
+
} catch {
|
|
126
|
+
/* keep the en-US-ish defaults */
|
|
127
|
+
}
|
|
128
|
+
const result = { group, decimal }
|
|
129
|
+
separatorCache.set(key, result)
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const DECIMAL_RE = /^-?\d+(\.\d+)?$/
|
|
134
|
+
|
|
135
|
+
function groupDigits(intDigits: string, groupSeparator: string): string {
|
|
136
|
+
return intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Render a canonical decimal string in a locale's notation — e.g. `"1234.56"`
|
|
141
|
+
* → `"1.234,56"` (de) / `"1,234.56"` (en). `grouped: false` omits the thousands
|
|
142
|
+
* separator (useful while a field is being edited). Precision-preserving: it
|
|
143
|
+
* splits the string and substitutes separators, never `Number()`.
|
|
144
|
+
*/
|
|
145
|
+
export function formatAmount(
|
|
146
|
+
canonical: string,
|
|
147
|
+
locale?: string,
|
|
148
|
+
options?: { grouped?: boolean },
|
|
149
|
+
): string {
|
|
150
|
+
if (canonical === '') return ''
|
|
151
|
+
if (!DECIMAL_RE.test(canonical)) return canonical
|
|
152
|
+
const grouped = options?.grouped !== false
|
|
153
|
+
const { group, decimal } = localeNumberSeparators(locale)
|
|
154
|
+
const negative = canonical.startsWith('-')
|
|
155
|
+
const body = negative ? canonical.slice(1) : canonical
|
|
156
|
+
const dotIndex = body.indexOf('.')
|
|
157
|
+
const intPart = dotIndex === -1 ? body : body.slice(0, dotIndex)
|
|
158
|
+
const fracPart = dotIndex === -1 ? undefined : body.slice(dotIndex + 1)
|
|
159
|
+
const intOut = grouped ? groupDigits(intPart, group) : intPart
|
|
160
|
+
const out = fracPart !== undefined ? `${intOut}${decimal}${fracPart}` : intOut
|
|
161
|
+
return negative ? `-${out}` : out
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parse locale-formatted user input back to a canonical decimal string — e.g.
|
|
166
|
+
* `"1.234,56"` (de) → `"1234.56"`. Strips the locale group separator,
|
|
167
|
+
* normalizes the locale decimal to `.`, and drops stray characters (symbols,
|
|
168
|
+
* spaces). Returns `""` for empty/invalid input.
|
|
169
|
+
*/
|
|
170
|
+
export function parseAmount(input: string, locale?: string): string {
|
|
171
|
+
const trimmed = input.trim()
|
|
172
|
+
if (trimmed === '') return ''
|
|
173
|
+
const { group, decimal } = localeNumberSeparators(locale)
|
|
174
|
+
const negative = trimmed.startsWith('-')
|
|
175
|
+
let normalized = trimmed.split(group).join('') // strip thousands separators
|
|
176
|
+
normalized = normalized.split(decimal).join('.') // locale decimal → '.'
|
|
177
|
+
normalized = normalized.replace(/[^\d.]/g, '') // drop symbols, spaces, stray chars
|
|
178
|
+
const firstDot = normalized.indexOf('.')
|
|
179
|
+
if (firstDot !== -1) {
|
|
180
|
+
// Collapse to a single decimal point.
|
|
181
|
+
normalized =
|
|
182
|
+
normalized.slice(0, firstDot + 1) + normalized.slice(firstDot + 1).replace(/\./g, '')
|
|
183
|
+
}
|
|
184
|
+
if (normalized === '' || normalized === '.') return ''
|
|
185
|
+
return negative ? `-${normalized}` : normalized
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Format a currency value for read-only display: the symbol placed by currency
|
|
190
|
+
* convention ({@link currencySymbolSide}) around the amount grouped per the
|
|
191
|
+
* viewer's locale ({@link formatAmount}). E.g. USD → `"$1,234.56"`, EUR (de
|
|
192
|
+
* viewer) → `"1.234,56 €"`. Returns a plain `"amount code"` string when the
|
|
193
|
+
* value can't be formatted.
|
|
194
|
+
*/
|
|
195
|
+
export function formatMoney(value: CurrencyValue, locale?: string): string {
|
|
196
|
+
const { amount, currency_code: code } = value
|
|
197
|
+
if (!amount || !code) return [amount, code].filter(Boolean).join(' ')
|
|
198
|
+
if (!DECIMAL_RE.test(amount)) return `${amount} ${code}`
|
|
199
|
+
const number = formatAmount(amount, locale, { grouped: true })
|
|
200
|
+
const symbol = currencySymbol(code, locale)
|
|
201
|
+
return currencySymbolSide(code) === 'suffix' ? `${number} ${symbol}` : `${symbol}${number}`
|
|
202
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { ProteosClient } from '../client.js'
|
|
2
|
+
import { PageIterator } from '../iterator.js'
|
|
3
|
+
import type { ListResult } from '../types/common.js'
|
|
4
|
+
import type {
|
|
5
|
+
CreateEntityRequest,
|
|
6
|
+
Entity,
|
|
7
|
+
EntityWithSchema,
|
|
8
|
+
ListEntitiesOptions,
|
|
9
|
+
UpdateEntityRequest,
|
|
10
|
+
} from './types.js'
|
|
11
|
+
|
|
12
|
+
const ENTITIES_BASE_PATH = '/meta/v1/entities'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Service for managing entity definitions.
|
|
16
|
+
* Entities define the structure of business objects in the system.
|
|
17
|
+
*/
|
|
18
|
+
export interface EntityService {
|
|
19
|
+
/**
|
|
20
|
+
* Lists entities with optional filtering.
|
|
21
|
+
* Returns an async iterator that automatically handles pagination.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Filter and pagination options
|
|
24
|
+
* @returns Async iterator over entities
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // Iterate over all entities
|
|
29
|
+
* for await (const entity of service.list()) {
|
|
30
|
+
* console.log(entity.slug);
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // With filtering
|
|
34
|
+
* for await (const entity of service.list({ module_slug: 'my-module' })) {
|
|
35
|
+
* console.log(entity.name);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
list(options?: ListEntitiesOptions): PageIterator<Entity, ListEntitiesOptions>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fetches a single page of entities.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Filter and pagination options (including `page`)
|
|
45
|
+
* @returns A single page of entities with pagination metadata
|
|
46
|
+
*/
|
|
47
|
+
listPage(options?: ListEntitiesOptions): Promise<ListResult<Entity>>
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Lists entities including their JSON Schema representation.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Filter and pagination options
|
|
53
|
+
* @returns Async iterator over entities with schema
|
|
54
|
+
*/
|
|
55
|
+
listWithSchema(options?: ListEntitiesOptions): PageIterator<EntityWithSchema, ListEntitiesOptions>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetches a single page of entities with their JSON Schema representation.
|
|
59
|
+
* Useful for integrating with paginated UI frameworks (e.g. React Query's
|
|
60
|
+
* useInfiniteQuery) that need direct access to a page's meta and data.
|
|
61
|
+
*
|
|
62
|
+
* @param options - Filter and pagination options
|
|
63
|
+
* @returns A single page of entities with schema
|
|
64
|
+
*/
|
|
65
|
+
listPageWithSchema(options?: ListEntitiesOptions): Promise<ListResult<EntityWithSchema>>
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets a single entity by slug.
|
|
69
|
+
*
|
|
70
|
+
* @param slug - Entity slug
|
|
71
|
+
* @returns The entity
|
|
72
|
+
* @throws {ProteosError} If entity not found (404)
|
|
73
|
+
*/
|
|
74
|
+
get(slug: string): Promise<Entity>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets an entity with its JSON Schema representation.
|
|
78
|
+
*
|
|
79
|
+
* @param slug - Entity slug
|
|
80
|
+
* @returns The entity with schema
|
|
81
|
+
* @throws {ProteosError} If entity not found (404)
|
|
82
|
+
*/
|
|
83
|
+
getWithSchema(slug: string): Promise<EntityWithSchema>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new entity.
|
|
87
|
+
*
|
|
88
|
+
* @param request - Entity creation request
|
|
89
|
+
* @returns The created entity
|
|
90
|
+
* @throws {ProteosError} If validation fails (400) or conflict (409)
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const entity = await service.create({
|
|
95
|
+
* slug: 'customers',
|
|
96
|
+
* name: 'Customer',
|
|
97
|
+
* description: 'Customer records',
|
|
98
|
+
* is_remote: false,
|
|
99
|
+
* module_slug: 'crm',
|
|
100
|
+
* attributes: [
|
|
101
|
+
* { name: 'id', type: 'string', meta: { format: 'uuid' }, label: 'ID', is_required: true, is_unique: true },
|
|
102
|
+
* { name: 'name', type: 'string', label: 'Name', is_required: true, is_unique: false },
|
|
103
|
+
* ],
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
create(request: CreateEntityRequest): Promise<Entity>
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates or updates an entity idempotently by slug. The URL slug wins on
|
|
111
|
+
* mismatch with the body's slug field. Backs the CLI/admin idempotent deploy
|
|
112
|
+
* path (`PUT /meta/v1/entities/:slug`).
|
|
113
|
+
*
|
|
114
|
+
* @param slug - Entity slug
|
|
115
|
+
* @param request - Full entity body
|
|
116
|
+
* @returns The created or updated entity
|
|
117
|
+
*/
|
|
118
|
+
upsert(slug: string, request: CreateEntityRequest): Promise<Entity>
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Updates an existing entity.
|
|
122
|
+
*
|
|
123
|
+
* @param slug - Entity slug
|
|
124
|
+
* @param request - Fields to update
|
|
125
|
+
* @returns The updated entity
|
|
126
|
+
* @throws {ProteosError} If entity not found (404) or validation fails (400)
|
|
127
|
+
*/
|
|
128
|
+
update(slug: string, request: UpdateEntityRequest): Promise<Entity>
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Deletes an entity.
|
|
132
|
+
*
|
|
133
|
+
* @param slug - Entity slug
|
|
134
|
+
* @throws {ProteosError} If entity not found (404)
|
|
135
|
+
*/
|
|
136
|
+
delete(slug: string): Promise<void>
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Implementation of EntityService.
|
|
141
|
+
*/
|
|
142
|
+
export class EntityServiceImpl implements EntityService {
|
|
143
|
+
constructor(private readonly client: ProteosClient) {}
|
|
144
|
+
|
|
145
|
+
list(options: ListEntitiesOptions = {}): PageIterator<Entity, ListEntitiesOptions> {
|
|
146
|
+
return new PageIterator((opts) => this.listPage(opts), options)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async listPage(options: ListEntitiesOptions = {}): Promise<ListResult<Entity>> {
|
|
150
|
+
return this.client.requestWithQuery<ListResult<Entity>>('GET', ENTITIES_BASE_PATH, options)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
listWithSchema(
|
|
154
|
+
options: ListEntitiesOptions = {},
|
|
155
|
+
): PageIterator<EntityWithSchema, ListEntitiesOptions> {
|
|
156
|
+
return new PageIterator((opts) => this.listPageWithSchema(opts), options)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async listPageWithSchema(
|
|
160
|
+
options: ListEntitiesOptions = {},
|
|
161
|
+
): Promise<ListResult<EntityWithSchema>> {
|
|
162
|
+
return this.client.requestWithQuery<ListResult<EntityWithSchema>>('GET', ENTITIES_BASE_PATH, {
|
|
163
|
+
...options,
|
|
164
|
+
with_schema: true,
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async get(slug: string): Promise<Entity> {
|
|
169
|
+
return this.client.request<Entity>('GET', `${ENTITIES_BASE_PATH}/${slug}`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getWithSchema(slug: string): Promise<EntityWithSchema> {
|
|
173
|
+
return this.client.requestWithQuery<EntityWithSchema>('GET', `${ENTITIES_BASE_PATH}/${slug}`, {
|
|
174
|
+
with_schema: true,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async create(request: CreateEntityRequest): Promise<Entity> {
|
|
179
|
+
return this.client.request<Entity>('POST', ENTITIES_BASE_PATH, request)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async upsert(slug: string, request: CreateEntityRequest): Promise<Entity> {
|
|
183
|
+
return this.client.request<Entity>('PUT', `${ENTITIES_BASE_PATH}/${slug}`, { ...request, slug })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async update(slug: string, request: UpdateEntityRequest): Promise<Entity> {
|
|
187
|
+
return this.client.request<Entity>('PATCH', `${ENTITIES_BASE_PATH}/${slug}`, request)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async delete(slug: string): Promise<void> {
|
|
191
|
+
await this.client.request<void>('DELETE', `${ENTITIES_BASE_PATH}/${slug}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logical operator used to combine filter elements/groups.
|
|
5
|
+
*/
|
|
6
|
+
export type LogicalOperator = 'and' | 'or'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Comparison operator for a single filter element. Mirrors
|
|
10
|
+
* `ComparisonOperator` in `packages/go/model/comparison-operator.go`.
|
|
11
|
+
*/
|
|
12
|
+
export type ComparisonOperator =
|
|
13
|
+
| 'eq'
|
|
14
|
+
| 'gt'
|
|
15
|
+
| 'lt'
|
|
16
|
+
| 'gte'
|
|
17
|
+
| 'lte'
|
|
18
|
+
| 'ne'
|
|
19
|
+
| 'in'
|
|
20
|
+
| 'not_in'
|
|
21
|
+
| 'contains'
|
|
22
|
+
| 'starts_with'
|
|
23
|
+
| 'ends_with'
|
|
24
|
+
| 'empty'
|
|
25
|
+
| 'not_empty'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Single filter element (atomic predicate).
|
|
29
|
+
*
|
|
30
|
+
* `value` is a string at the wire level — pipe-joined for `in` / `not_in`,
|
|
31
|
+
* ignored for `empty` / `not_empty`. This matches the data-service URL query
|
|
32
|
+
* convention so the same predicate flows through both the GET-with-filters
|
|
33
|
+
* path and the layout `visible_when` JSONB path without a translation layer.
|
|
34
|
+
*/
|
|
35
|
+
export interface FilterElement {
|
|
36
|
+
field: string
|
|
37
|
+
value: string
|
|
38
|
+
operator: ComparisonOperator
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Filter group with logical operator. Groups can be nested.
|
|
43
|
+
*/
|
|
44
|
+
export interface FilterGroup {
|
|
45
|
+
logical_operator: LogicalOperator
|
|
46
|
+
elements?: FilterElement[]
|
|
47
|
+
groups?: FilterGroup[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const FilterElementSchema = z.object({
|
|
51
|
+
field: z.string(),
|
|
52
|
+
value: z.string(),
|
|
53
|
+
operator: z.enum([
|
|
54
|
+
'eq',
|
|
55
|
+
'gt',
|
|
56
|
+
'lt',
|
|
57
|
+
'gte',
|
|
58
|
+
'lte',
|
|
59
|
+
'ne',
|
|
60
|
+
'in',
|
|
61
|
+
'not_in',
|
|
62
|
+
'contains',
|
|
63
|
+
'starts_with',
|
|
64
|
+
'ends_with',
|
|
65
|
+
'empty',
|
|
66
|
+
'not_empty',
|
|
67
|
+
]),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const FilterGroupSchema: z.ZodType<FilterGroup> = z.lazy(() =>
|
|
71
|
+
z.object({
|
|
72
|
+
logical_operator: z.enum(['and', 'or']),
|
|
73
|
+
elements: z.array(FilterElementSchema).optional(),
|
|
74
|
+
groups: z.array(FilterGroupSchema).optional(),
|
|
75
|
+
}),
|
|
76
|
+
) as unknown as z.ZodType<FilterGroup>
|