@object-ui/data-objectstack 17.3.0 → 17.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +888 -0
  2. package/README.md +3 -11
  3. package/dist/index.cjs +1511 -1115
  4. package/dist/index.d.cts +1222 -824
  5. package/dist/index.d.ts +1222 -824
  6. package/dist/index.js +1503 -1111
  7. package/package.json +5 -6
  8. package/src/aggregate-capability.test.ts +0 -177
  9. package/src/bulkDelete.test.ts +0 -75
  10. package/src/bulkUpdate.test.ts +0 -93
  11. package/src/cache/MetadataCache.test.ts +0 -477
  12. package/src/cache/MetadataCache.ts +0 -289
  13. package/src/cloud.ts +0 -109
  14. package/src/connection.test.ts +0 -188
  15. package/src/contracts.ts +0 -115
  16. package/src/errors.test.ts +0 -426
  17. package/src/errors.ts +0 -293
  18. package/src/exportDownload.test.ts +0 -141
  19. package/src/filter-entry-translation.test.ts +0 -360
  20. package/src/filter-operator-ast-parity.test.ts +0 -71
  21. package/src/getObjectSchema.test.ts +0 -145
  22. package/src/importJob.test.ts +0 -71
  23. package/src/index.ts +0 -3658
  24. package/src/integration.ts +0 -200
  25. package/src/listViews.test.ts +0 -175
  26. package/src/metadata-client.test.ts +0 -303
  27. package/src/metadata-client.ts +0 -849
  28. package/src/occ.test.ts +0 -101
  29. package/src/onMutation.test.ts +0 -562
  30. package/src/onWriteWarning.test.ts +0 -273
  31. package/src/orderby-serialization.test.ts +0 -115
  32. package/src/queryDataset.test.ts +0 -132
  33. package/src/searchAll.test.ts +0 -120
  34. package/src/security.ts +0 -243
  35. package/src/spec-symbol-batch6.test.ts +0 -306
  36. package/src/studio.ts +0 -152
  37. package/src/userState.test.ts +0 -315
  38. package/src/userState.ts +0 -238
  39. package/src/v3-compat.test.ts +0 -240
  40. package/src/validation-error.test.ts +0 -94
@@ -1,240 +0,0 @@
1
- /**
2
- * v3.0.0 compatibility tests for @objectstack dependencies.
3
- * Validates that all integration modules work correctly with the spec.
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- import { validatePluginContract, generateContractManifest } from './contracts';
7
- import { IntegrationManager } from './integration';
8
- import { SecurityManager } from './security';
9
- import { CloudOperations } from './cloud';
10
- import { createDefaultCanvasConfig, snapToGrid, calculateAutoLayout } from './studio';
11
-
12
- describe('v3.0.0 Compatibility', () => {
13
- describe('Cloud namespace (replacing Hub)', () => {
14
- it('should create cloud operations instance', () => {
15
- const ops = new CloudOperations(() => ({}));
16
- expect(ops).toBeDefined();
17
- });
18
-
19
- it('should handle deploy when client has no cloud support', async () => {
20
- const ops = new CloudOperations(() => ({}));
21
- const result = await ops.deploy('app-1', { environment: 'production' });
22
- expect(result).toHaveProperty('deploymentId');
23
- expect(result).toHaveProperty('status');
24
- });
25
-
26
- it('should handle marketplace search when client has no cloud support', async () => {
27
- const ops = new CloudOperations(() => ({}));
28
- const results = await ops.searchMarketplace('grid');
29
- expect(Array.isArray(results)).toBe(true);
30
- });
31
- });
32
-
33
- describe('Contracts module', () => {
34
- it('should validate a valid plugin contract', () => {
35
- const contract = {
36
- name: 'my-plugin',
37
- version: '1.0.0',
38
- exports: [{ name: 'MyComponent', type: 'component' as const }],
39
- permissions: ['data.read'],
40
- };
41
- const result = validatePluginContract(contract);
42
- expect(result.valid).toBe(true);
43
- expect(result.errors).toHaveLength(0);
44
- });
45
-
46
- it('should reject invalid plugin contract', () => {
47
- const contract = {
48
- name: '',
49
- version: 'invalid',
50
- exports: [],
51
- };
52
- const result = validatePluginContract(contract);
53
- expect(result.valid).toBe(false);
54
- expect(result.errors.length).toBeGreaterThan(0);
55
- });
56
-
57
- it('should generate contract manifest', () => {
58
- const contract = {
59
- name: 'test-plugin',
60
- version: '2.0.0',
61
- exports: [{ name: 'GridView', type: 'component' as const, description: 'A grid view' }],
62
- permissions: ['data.read', 'data.write'],
63
- };
64
- const manifest = generateContractManifest(contract);
65
- expect(manifest.$schema).toBeDefined();
66
- expect(manifest.name).toBe('test-plugin');
67
- expect(manifest.version).toBe('2.0.0');
68
- });
69
- });
70
-
71
- describe('Integration module', () => {
72
- it('should register and retrieve integrations', () => {
73
- const manager = new IntegrationManager();
74
- manager.register('slack-1', {
75
- provider: 'slack',
76
- enabled: true,
77
- config: { webhookUrl: 'https://hooks.slack.com/test' },
78
- triggers: [{ event: 'record.created' }],
79
- });
80
-
81
- const all = manager.getAll();
82
- expect(all.size).toBe(1);
83
- });
84
-
85
- it('should filter integrations by event', () => {
86
- const manager = new IntegrationManager();
87
- manager.register('webhook-1', {
88
- provider: 'webhook',
89
- enabled: true,
90
- config: { url: 'https://example.com/hook', method: 'POST' },
91
- triggers: [{ event: 'record.created' }],
92
- });
93
- manager.register('webhook-2', {
94
- provider: 'webhook',
95
- enabled: true,
96
- config: { url: 'https://example.com/hook2', method: 'POST' },
97
- triggers: [{ event: 'record.deleted' }],
98
- });
99
-
100
- const createMatches = manager.getForEvent('record.created');
101
- expect(createMatches).toHaveLength(1);
102
- });
103
-
104
- it('should unregister integrations', () => {
105
- const manager = new IntegrationManager();
106
- manager.register('test', {
107
- provider: 'webhook',
108
- enabled: true,
109
- config: { url: 'https://example.com', method: 'POST' },
110
- });
111
- manager.unregister('test');
112
- expect(manager.getAll().size).toBe(0);
113
- });
114
- });
115
-
116
- describe('Security module', () => {
117
- it('should generate CSP header', () => {
118
- const manager = new SecurityManager({
119
- csp: {
120
- scriptSrc: ["'self'", "'unsafe-inline'"],
121
- styleSrc: ["'self'", "'unsafe-inline'"],
122
- imgSrc: ["'self'", 'data:', 'https:'],
123
- connectSrc: ["'self'", 'https://api.example.com'],
124
- fontSrc: ["'self'"],
125
- },
126
- });
127
-
128
- const header = manager.generateCSPHeader();
129
- expect(header).toContain("script-src 'self' 'unsafe-inline'");
130
- expect(header).toContain("connect-src 'self' https://api.example.com");
131
- });
132
-
133
- it('should record and filter audit logs', () => {
134
- const manager = new SecurityManager({
135
- auditLog: {
136
- enabled: true,
137
- events: ['data.create', 'data.read', 'auth.login'],
138
- destination: 'console',
139
- },
140
- });
141
-
142
- manager.recordAudit({ event: 'data.create', userId: 'user-1', resource: 'accounts' });
143
- manager.recordAudit({ event: 'auth.login', userId: 'user-2' });
144
- manager.recordAudit({ event: 'data.create', userId: 'user-1', resource: 'contacts' });
145
-
146
- const allLogs = manager.getAuditLog();
147
- expect(allLogs).toHaveLength(3);
148
-
149
- const userLogs = manager.getAuditLog({ userId: 'user-1' });
150
- expect(userLogs).toHaveLength(2);
151
-
152
- const loginLogs = manager.getAuditLog({ event: 'auth.login' });
153
- expect(loginLogs).toHaveLength(1);
154
- });
155
-
156
- it('should mask record data', () => {
157
- const manager = new SecurityManager({
158
- dataMasking: {
159
- rules: [
160
- { field: 'ssn', strategy: 'partial', visibleChars: 4 },
161
- { field: 'password', strategy: 'redact' },
162
- { field: 'email', strategy: 'full' },
163
- { field: 'phone', strategy: 'partial', visibleChars: 3, exemptRoles: ['admin'] },
164
- ],
165
- },
166
- });
167
-
168
- const record = {
169
- ssn: '123-45-6789',
170
- password: 'secret123',
171
- email: 'john@example.com',
172
- phone: '555-1234',
173
- name: 'John',
174
- };
175
-
176
- const masked = manager.maskRecord(record);
177
- expect(masked.ssn).toBe('123-*******');
178
- expect(masked.password).toBe('[REDACTED]');
179
- expect(masked.email).toBe('****************');
180
- expect(masked.name).toBe('John'); // Not masked
181
-
182
- // Admin sees phone unmasked
183
- const adminMasked = manager.maskRecord(record, ['admin']);
184
- expect(adminMasked.phone).toBe('555-1234');
185
- });
186
- });
187
-
188
- describe('Studio module', () => {
189
- it('should create default canvas config', () => {
190
- const config = createDefaultCanvasConfig();
191
- expect(config.width).toBe(1200);
192
- expect(config.height).toBe(800);
193
- expect(config.snapToGrid).toBe(true);
194
- expect(config.zoom.min).toBe(0.25);
195
- expect(config.zoom.max).toBe(3);
196
- });
197
-
198
- it('should create canvas config with overrides', () => {
199
- const config = createDefaultCanvasConfig({ width: 1600, showMinimap: true });
200
- expect(config.width).toBe(1600);
201
- expect(config.showMinimap).toBe(true);
202
- expect(config.height).toBe(800); // Default
203
- });
204
-
205
- it('should snap positions to grid', () => {
206
- expect(snapToGrid(13, 27, 8)).toEqual({ x: 16, y: 24 });
207
- expect(snapToGrid(0, 0, 8)).toEqual({ x: 0, y: 0 });
208
- expect(snapToGrid(4, 4, 8)).toEqual({ x: 8, y: 8 });
209
- });
210
-
211
- it('should calculate auto-layout positions', () => {
212
- const items = [
213
- { id: '1', width: 200, height: 150 },
214
- { id: '2', width: 200, height: 100 },
215
- { id: '3', width: 200, height: 200 },
216
- ];
217
- const positions = calculateAutoLayout(items, 1200);
218
- expect(positions).toHaveLength(3);
219
- expect(positions[0].x).toBe(40); // padding
220
- expect(positions[0].y).toBe(40); // padding
221
- expect(positions[1].x).toBeGreaterThan(positions[0].x);
222
- });
223
- });
224
-
225
- describe('PaginatedResult API (records/total/hasMore)', () => {
226
- it('should support v3.0.0 PaginatedResult fields', () => {
227
- // Verify the QueryResult type supports records/total/hasMore
228
- const result = {
229
- data: [{ id: '1' }],
230
- total: 10,
231
- page: 1,
232
- pageSize: 5,
233
- hasMore: true,
234
- };
235
- expect(result.data).toHaveLength(1);
236
- expect(result.total).toBe(10);
237
- expect(result.hasMore).toBe(true);
238
- });
239
- });
240
- });
@@ -1,94 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect } from 'vitest';
10
-
11
- import { normaliseClientError } from './index';
12
- import { DataApiValidationError } from './errors';
13
-
14
- /**
15
- * The server has always sent per-field rejection detail; the adapter used to
16
- * hand callers the raw client error, so `fields[]` had no typed home and every
17
- * form fell back to one undirected toast.
18
- */
19
- describe('normaliseClientError — VALIDATION_FAILED', () => {
20
- // `@objectstack/rest` serves a thrown validation failure as 400 with `fields[]`
21
- // verbatim; `@objectstack/client` sets `error.details` to the parsed body's
22
- // `details` OR — as here, since the envelope has no such key — the whole body.
23
- const upstream = () =>
24
- Object.assign(new Error('Name is required; Stage is not a valid option'), {
25
- code: 'VALIDATION_FAILED',
26
- httpStatus: 400,
27
- details: {
28
- error: 'Name is required; Stage is not a valid option',
29
- code: 'VALIDATION_FAILED',
30
- fields: [
31
- { field: 'name', code: 'required', message: 'Name is required' },
32
- { field: 'stage', code: 'invalid_option', message: 'Stage is not a valid option' },
33
- ],
34
- object: 'crm_opportunity',
35
- },
36
- });
37
-
38
- it('wraps it into a typed DataApiValidationError', () => {
39
- const normalised = normaliseClientError(upstream());
40
- expect(normalised).toBeInstanceOf(DataApiValidationError);
41
- });
42
-
43
- it('carries every field entry through', () => {
44
- const normalised = normaliseClientError(upstream()) as DataApiValidationError;
45
- expect(normalised.validationErrors).toEqual([
46
- { field: 'name', message: 'Name is required' },
47
- { field: 'stage', message: 'Stage is not a valid option' },
48
- ]);
49
- });
50
-
51
- it('keeps the server message verbatim — it names every field', () => {
52
- const normalised = normaliseClientError(upstream()) as DataApiValidationError;
53
- expect(normalised.message).toBe('Name is required; Stage is not a valid option');
54
- });
55
-
56
- it('exposes the first offending field on `.field`', () => {
57
- const normalised = normaliseClientError(upstream()) as DataApiValidationError;
58
- expect(normalised.field).toBe('name');
59
- });
60
-
61
- it('falls back to the machine code when an entry has no message', () => {
62
- const err = Object.assign(new Error('Validation failed'), {
63
- code: 'VALIDATION_FAILED',
64
- details: { fields: [{ field: 'name', code: 'required' }] },
65
- });
66
- const normalised = normaliseClientError(err) as DataApiValidationError;
67
- expect(normalised.validationErrors).toEqual([{ field: 'name', message: 'required' }]);
68
- });
69
-
70
- it('drops entries with no field rather than inventing one', () => {
71
- const err = Object.assign(new Error('Validation failed'), {
72
- code: 'VALIDATION_FAILED',
73
- details: { fields: [{ message: 'something is wrong' }] },
74
- });
75
- const normalised = normaliseClientError(err) as DataApiValidationError;
76
- expect(normalised.validationErrors).toEqual([]);
77
- });
78
-
79
- it('still wraps when the error carries `fields` directly (hand-rolled shape)', () => {
80
- // The server duck-types on `code`/`name`, so a hook throwing the bare shape
81
- // is served identically — the client must not be pickier than the server.
82
- const err = Object.assign(new Error('nope'), {
83
- name: 'ValidationError',
84
- fields: [{ field: 'amount', message: 'Amount must be positive' }],
85
- });
86
- const normalised = normaliseClientError(err) as DataApiValidationError;
87
- expect(normalised.validationErrors).toEqual([{ field: 'amount', message: 'Amount must be positive' }]);
88
- });
89
-
90
- it('leaves an unrelated 400 untouched', () => {
91
- const err = Object.assign(new Error('bad request'), { code: 'BAD_REQUEST', httpStatus: 400 });
92
- expect(normaliseClientError(err)).toBe(err);
93
- });
94
- });