@objectstack/objectql 4.0.4 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +726 -1117
- package/dist/index.d.ts +726 -1117
- package/dist/index.js +2316 -374
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2305 -372
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -720
- package/src/datasource-mapping.test.ts +0 -181
- package/src/engine.test.ts +0 -613
- package/src/engine.ts +0 -1668
- package/src/index.ts +0 -41
- package/src/kernel-factory.ts +0 -48
- package/src/metadata-facade.ts +0 -96
- package/src/plugin.integration.test.ts +0 -995
- package/src/plugin.ts +0 -534
- package/src/protocol-data.test.ts +0 -245
- package/src/protocol-discovery.test.ts +0 -213
- package/src/protocol-feed.test.ts +0 -303
- package/src/protocol-meta.test.ts +0 -440
- package/src/protocol.ts +0 -1242
- package/src/registry.test.ts +0 -494
- package/src/registry.ts +0 -716
- package/src/util.test.ts +0 -226
- package/src/util.ts +0 -219
- package/tsconfig.json +0 -10
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
4
|
-
import { ObjectStackProtocolImplementation } from './protocol.js';
|
|
5
|
-
import { ObjectQL } from './engine.js';
|
|
6
|
-
import type { IFeedService } from '@objectstack/spec/contracts';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Mock IFeedService for testing feed route handlers.
|
|
10
|
-
*/
|
|
11
|
-
function createMockFeedService(): IFeedService {
|
|
12
|
-
return {
|
|
13
|
-
listFeed: vi.fn().mockResolvedValue({
|
|
14
|
-
items: [{ id: 'feed_1', type: 'comment', body: 'Hello world', createdAt: '2026-01-01T00:00:00Z' }],
|
|
15
|
-
total: 1,
|
|
16
|
-
hasMore: false,
|
|
17
|
-
}),
|
|
18
|
-
createFeedItem: vi.fn().mockResolvedValue({
|
|
19
|
-
id: 'feed_new',
|
|
20
|
-
type: 'comment',
|
|
21
|
-
body: 'New comment',
|
|
22
|
-
createdAt: '2026-01-01T00:00:00Z',
|
|
23
|
-
}),
|
|
24
|
-
updateFeedItem: vi.fn().mockResolvedValue({
|
|
25
|
-
id: 'feed_1',
|
|
26
|
-
type: 'comment',
|
|
27
|
-
body: 'Updated comment',
|
|
28
|
-
createdAt: '2026-01-01T00:00:00Z',
|
|
29
|
-
}),
|
|
30
|
-
deleteFeedItem: vi.fn().mockResolvedValue(undefined),
|
|
31
|
-
getFeedItem: vi.fn().mockResolvedValue({
|
|
32
|
-
id: 'feed_1',
|
|
33
|
-
type: 'comment',
|
|
34
|
-
body: 'Hello world',
|
|
35
|
-
createdAt: '2026-01-01T00:00:00Z',
|
|
36
|
-
}),
|
|
37
|
-
addReaction: vi.fn().mockResolvedValue([
|
|
38
|
-
{ emoji: '👍', users: ['current_user'], count: 1 },
|
|
39
|
-
]),
|
|
40
|
-
removeReaction: vi.fn().mockResolvedValue([]),
|
|
41
|
-
subscribe: vi.fn().mockResolvedValue({
|
|
42
|
-
id: 'sub_1',
|
|
43
|
-
object: 'account',
|
|
44
|
-
recordId: 'rec_123',
|
|
45
|
-
userId: 'current_user',
|
|
46
|
-
events: ['all'],
|
|
47
|
-
channels: ['in_app'],
|
|
48
|
-
}),
|
|
49
|
-
unsubscribe: vi.fn().mockResolvedValue(true),
|
|
50
|
-
getSubscription: vi.fn().mockResolvedValue(null),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
describe('ObjectStackProtocolImplementation - Feed Operations', () => {
|
|
55
|
-
let protocol: ObjectStackProtocolImplementation;
|
|
56
|
-
let engine: ObjectQL;
|
|
57
|
-
let feedService: IFeedService;
|
|
58
|
-
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
engine = new ObjectQL();
|
|
61
|
-
feedService = createMockFeedService();
|
|
62
|
-
protocol = new ObjectStackProtocolImplementation(engine, undefined, () => feedService);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ==========================================
|
|
66
|
-
// Discovery
|
|
67
|
-
// ==========================================
|
|
68
|
-
|
|
69
|
-
it('should show feed service as unavailable when not registered', async () => {
|
|
70
|
-
const protocolNoFeed = new ObjectStackProtocolImplementation(engine);
|
|
71
|
-
const discovery = await protocolNoFeed.getDiscovery();
|
|
72
|
-
|
|
73
|
-
expect(discovery.services.feed).toBeDefined();
|
|
74
|
-
expect(discovery.services.feed.enabled).toBe(false);
|
|
75
|
-
expect(discovery.services.feed.status).toBe('unavailable');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should show feed service as available when registered', async () => {
|
|
79
|
-
const mockServices = new Map<string, any>();
|
|
80
|
-
mockServices.set('feed', {});
|
|
81
|
-
const protocolWithFeed = new ObjectStackProtocolImplementation(engine, () => mockServices, () => feedService);
|
|
82
|
-
const discovery = await protocolWithFeed.getDiscovery();
|
|
83
|
-
|
|
84
|
-
expect(discovery.services.feed).toBeDefined();
|
|
85
|
-
expect(discovery.services.feed.enabled).toBe(true);
|
|
86
|
-
expect(discovery.services.feed.status).toBe('available');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ==========================================
|
|
90
|
-
// Feed CRUD
|
|
91
|
-
// ==========================================
|
|
92
|
-
|
|
93
|
-
it('listFeed should delegate to feedService.listFeed', async () => {
|
|
94
|
-
const result = await protocol.listFeed({ object: 'account', recordId: 'rec_123' });
|
|
95
|
-
|
|
96
|
-
expect(result.success).toBe(true);
|
|
97
|
-
expect(result.data.items).toHaveLength(1);
|
|
98
|
-
expect(feedService.listFeed).toHaveBeenCalledWith(
|
|
99
|
-
expect.objectContaining({ object: 'account', recordId: 'rec_123' })
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('createFeedItem should delegate to feedService.createFeedItem', async () => {
|
|
104
|
-
const result = await protocol.createFeedItem({
|
|
105
|
-
object: 'account',
|
|
106
|
-
recordId: 'rec_123',
|
|
107
|
-
type: 'comment',
|
|
108
|
-
body: 'New comment',
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(result.success).toBe(true);
|
|
112
|
-
expect(result.data.id).toBe('feed_new');
|
|
113
|
-
expect(feedService.createFeedItem).toHaveBeenCalledWith(
|
|
114
|
-
expect.objectContaining({ object: 'account', recordId: 'rec_123', type: 'comment', body: 'New comment' })
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('updateFeedItem should delegate to feedService.updateFeedItem', async () => {
|
|
119
|
-
const result = await protocol.updateFeedItem({
|
|
120
|
-
object: 'account',
|
|
121
|
-
recordId: 'rec_123',
|
|
122
|
-
feedId: 'feed_1',
|
|
123
|
-
body: 'Updated',
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
expect(result.success).toBe(true);
|
|
127
|
-
expect(result.data.body).toBe('Updated comment');
|
|
128
|
-
expect(feedService.updateFeedItem).toHaveBeenCalledWith('feed_1', expect.objectContaining({ body: 'Updated' }));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('deleteFeedItem should delegate to feedService.deleteFeedItem', async () => {
|
|
132
|
-
const result = await protocol.deleteFeedItem({
|
|
133
|
-
object: 'account',
|
|
134
|
-
recordId: 'rec_123',
|
|
135
|
-
feedId: 'feed_1',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(result.success).toBe(true);
|
|
139
|
-
expect(result.data.feedId).toBe('feed_1');
|
|
140
|
-
expect(feedService.deleteFeedItem).toHaveBeenCalledWith('feed_1');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ==========================================
|
|
144
|
-
// Reactions
|
|
145
|
-
// ==========================================
|
|
146
|
-
|
|
147
|
-
it('addReaction should delegate to feedService.addReaction', async () => {
|
|
148
|
-
const result = await protocol.addReaction({
|
|
149
|
-
object: 'account',
|
|
150
|
-
recordId: 'rec_123',
|
|
151
|
-
feedId: 'feed_1',
|
|
152
|
-
emoji: '👍',
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
expect(result.success).toBe(true);
|
|
156
|
-
expect(result.data.reactions).toHaveLength(1);
|
|
157
|
-
expect(feedService.addReaction).toHaveBeenCalledWith('feed_1', '👍', 'current_user');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('removeReaction should delegate to feedService.removeReaction', async () => {
|
|
161
|
-
const result = await protocol.removeReaction({
|
|
162
|
-
object: 'account',
|
|
163
|
-
recordId: 'rec_123',
|
|
164
|
-
feedId: 'feed_1',
|
|
165
|
-
emoji: '👍',
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
expect(result.success).toBe(true);
|
|
169
|
-
expect(result.data.reactions).toHaveLength(0);
|
|
170
|
-
expect(feedService.removeReaction).toHaveBeenCalledWith('feed_1', '👍', 'current_user');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ==========================================
|
|
174
|
-
// Pin / Star
|
|
175
|
-
// ==========================================
|
|
176
|
-
|
|
177
|
-
it('pinFeedItem should verify item exists and return pinned status', async () => {
|
|
178
|
-
const result = await protocol.pinFeedItem({
|
|
179
|
-
object: 'account',
|
|
180
|
-
recordId: 'rec_123',
|
|
181
|
-
feedId: 'feed_1',
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
expect(result.success).toBe(true);
|
|
185
|
-
expect(result.data.feedId).toBe('feed_1');
|
|
186
|
-
expect(result.data.pinned).toBe(true);
|
|
187
|
-
expect(result.data.pinnedAt).toBeDefined();
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('unpinFeedItem should verify item exists and return unpinned status', async () => {
|
|
191
|
-
const result = await protocol.unpinFeedItem({
|
|
192
|
-
object: 'account',
|
|
193
|
-
recordId: 'rec_123',
|
|
194
|
-
feedId: 'feed_1',
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
expect(result.success).toBe(true);
|
|
198
|
-
expect(result.data.feedId).toBe('feed_1');
|
|
199
|
-
expect(result.data.pinned).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('starFeedItem should verify item exists and return starred status', async () => {
|
|
203
|
-
const result = await protocol.starFeedItem({
|
|
204
|
-
object: 'account',
|
|
205
|
-
recordId: 'rec_123',
|
|
206
|
-
feedId: 'feed_1',
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
expect(result.success).toBe(true);
|
|
210
|
-
expect(result.data.feedId).toBe('feed_1');
|
|
211
|
-
expect(result.data.starred).toBe(true);
|
|
212
|
-
expect(result.data.starredAt).toBeDefined();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('unstarFeedItem should verify item exists and return unstarred status', async () => {
|
|
216
|
-
const result = await protocol.unstarFeedItem({
|
|
217
|
-
object: 'account',
|
|
218
|
-
recordId: 'rec_123',
|
|
219
|
-
feedId: 'feed_1',
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
expect(result.success).toBe(true);
|
|
223
|
-
expect(result.data.feedId).toBe('feed_1');
|
|
224
|
-
expect(result.data.starred).toBe(false);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// ==========================================
|
|
228
|
-
// Search & Changelog
|
|
229
|
-
// ==========================================
|
|
230
|
-
|
|
231
|
-
it('searchFeed should filter items by query text', async () => {
|
|
232
|
-
const result = await protocol.searchFeed({
|
|
233
|
-
object: 'account',
|
|
234
|
-
recordId: 'rec_123',
|
|
235
|
-
query: 'hello',
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
expect(result.success).toBe(true);
|
|
239
|
-
expect(result.data.items).toHaveLength(1);
|
|
240
|
-
expect(result.data.hasMore).toBe(false);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('getChangelog should return field change entries', async () => {
|
|
244
|
-
const result = await protocol.getChangelog({
|
|
245
|
-
object: 'account',
|
|
246
|
-
recordId: 'rec_123',
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
expect(result.success).toBe(true);
|
|
250
|
-
expect(result.data.entries).toBeDefined();
|
|
251
|
-
expect(feedService.listFeed).toHaveBeenCalledWith(
|
|
252
|
-
expect.objectContaining({ filter: 'changes_only' })
|
|
253
|
-
);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// ==========================================
|
|
257
|
-
// Subscriptions
|
|
258
|
-
// ==========================================
|
|
259
|
-
|
|
260
|
-
it('feedSubscribe should delegate to feedService.subscribe', async () => {
|
|
261
|
-
const result = await protocol.feedSubscribe({
|
|
262
|
-
object: 'account',
|
|
263
|
-
recordId: 'rec_123',
|
|
264
|
-
events: ['all'],
|
|
265
|
-
channels: ['in_app'],
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
expect(result.success).toBe(true);
|
|
269
|
-
expect(result.data.object).toBe('account');
|
|
270
|
-
expect(feedService.subscribe).toHaveBeenCalledWith(
|
|
271
|
-
expect.objectContaining({ object: 'account', recordId: 'rec_123' })
|
|
272
|
-
);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('feedUnsubscribe should delegate to feedService.unsubscribe', async () => {
|
|
276
|
-
const result = await protocol.feedUnsubscribe({
|
|
277
|
-
object: 'account',
|
|
278
|
-
recordId: 'rec_123',
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
expect(result.success).toBe(true);
|
|
282
|
-
expect(result.data.unsubscribed).toBe(true);
|
|
283
|
-
expect(feedService.unsubscribe).toHaveBeenCalledWith('account', 'rec_123', 'current_user');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// ==========================================
|
|
287
|
-
// Error handling
|
|
288
|
-
// ==========================================
|
|
289
|
-
|
|
290
|
-
it('should throw when feed service is not available', async () => {
|
|
291
|
-
const protocolNoFeed = new ObjectStackProtocolImplementation(engine);
|
|
292
|
-
|
|
293
|
-
await expect(protocolNoFeed.listFeed({ object: 'a', recordId: 'b' }))
|
|
294
|
-
.rejects.toThrow('Feed service not available');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('pinFeedItem should throw when feed item not found', async () => {
|
|
298
|
-
(feedService.getFeedItem as any).mockResolvedValue(null);
|
|
299
|
-
|
|
300
|
-
await expect(protocol.pinFeedItem({ object: 'a', recordId: 'b', feedId: 'nonexistent' }))
|
|
301
|
-
.rejects.toThrow('Feed item nonexistent not found');
|
|
302
|
-
});
|
|
303
|
-
});
|