@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.
Files changed (94) hide show
  1. package/LICENSE +40 -0
  2. package/dist/chunk-7RGN4E22.cjs +1185 -0
  3. package/dist/chunk-7RGN4E22.cjs.map +1 -0
  4. package/dist/chunk-XJP5WCRZ.js +1125 -0
  5. package/dist/chunk-XJP5WCRZ.js.map +1 -0
  6. package/dist/index.cjs +2384 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +5225 -0
  9. package/dist/index.d.ts +5225 -0
  10. package/dist/index.js +2146 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/meta/index.cjs +204 -0
  13. package/dist/meta/index.cjs.map +1 -0
  14. package/dist/meta/index.d.cts +2 -0
  15. package/dist/meta/index.d.ts +2 -0
  16. package/dist/meta/index.js +3 -0
  17. package/dist/meta/index.js.map +1 -0
  18. package/dist/types-BNsjfU8N.d.cts +3299 -0
  19. package/dist/types-BNsjfU8N.d.ts +3299 -0
  20. package/package.json +86 -0
  21. package/src/agent/agents.ts +53 -0
  22. package/src/agent/index.ts +134 -0
  23. package/src/agent/mcp-servers.ts +102 -0
  24. package/src/agent/prompts.ts +80 -0
  25. package/src/agent/session-types.ts +397 -0
  26. package/src/agent/sessions.ts +197 -0
  27. package/src/agent/skills.ts +89 -0
  28. package/src/agent/tools.ts +53 -0
  29. package/src/agent/types.ts +362 -0
  30. package/src/auth/index.ts +111 -0
  31. package/src/auth/me.ts +46 -0
  32. package/src/auth/organizations.ts +128 -0
  33. package/src/auth/platform-entities.ts +78 -0
  34. package/src/auth/roles.ts +213 -0
  35. package/src/auth/types.ts +294 -0
  36. package/src/auth/users.ts +226 -0
  37. package/src/client.ts +441 -0
  38. package/src/connector/index.ts +120 -0
  39. package/src/connector/types.ts +150 -0
  40. package/src/conversation/index.ts +297 -0
  41. package/src/conversation/types.ts +590 -0
  42. package/src/conversation/voice.ts +123 -0
  43. package/src/data/index.ts +53 -0
  44. package/src/data/queries.ts +66 -0
  45. package/src/data/records.ts +122 -0
  46. package/src/data/types.ts +89 -0
  47. package/src/errors.ts +148 -0
  48. package/src/events/index.ts +172 -0
  49. package/src/events/types.ts +77 -0
  50. package/src/functions/actions.ts +95 -0
  51. package/src/functions/index.ts +32 -0
  52. package/src/functions/types.ts +71 -0
  53. package/src/http/index.ts +2 -0
  54. package/src/http/query-params.ts +106 -0
  55. package/src/index.ts +598 -0
  56. package/src/iterator.ts +183 -0
  57. package/src/knowledge/graph.ts +35 -0
  58. package/src/knowledge/index.ts +104 -0
  59. package/src/knowledge/labels.ts +70 -0
  60. package/src/knowledge/links.ts +65 -0
  61. package/src/knowledge/nodes.ts +198 -0
  62. package/src/knowledge/record-links.ts +66 -0
  63. package/src/knowledge/types.ts +569 -0
  64. package/src/meta/apps.ts +107 -0
  65. package/src/meta/components.ts +124 -0
  66. package/src/meta/currency/index.ts +202 -0
  67. package/src/meta/entities.ts +193 -0
  68. package/src/meta/filters.ts +76 -0
  69. package/src/meta/index.ts +227 -0
  70. package/src/meta/layout/common-props.ts +93 -0
  71. package/src/meta/layout/control-registry.json +70 -0
  72. package/src/meta/layout/control-registry.ts +92 -0
  73. package/src/meta/layout/elements.ts +203 -0
  74. package/src/meta/layout/index.ts +41 -0
  75. package/src/meta/layout/page-layout.ts +35 -0
  76. package/src/meta/layout/size-value.ts +27 -0
  77. package/src/meta/list-views.ts +109 -0
  78. package/src/meta/lists.ts +104 -0
  79. package/src/meta/menu-configurations.ts +128 -0
  80. package/src/meta/modules.ts +159 -0
  81. package/src/meta/pages.ts +106 -0
  82. package/src/meta/types.ts +1115 -0
  83. package/src/meta/variables.ts +98 -0
  84. package/src/storage/files.ts +183 -0
  85. package/src/storage/index.ts +33 -0
  86. package/src/storage/types.ts +70 -0
  87. package/src/types/common.ts +143 -0
  88. package/src/types/index.ts +28 -0
  89. package/src/types/options.ts +95 -0
  90. package/src/workflow/executions.ts +99 -0
  91. package/src/workflow/index.ts +109 -0
  92. package/src/workflow/node-types.ts +50 -0
  93. package/src/workflow/types.ts +658 -0
  94. package/src/workflow/workflows.ts +152 -0
@@ -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>