@party-stack/foundry-ontology 0.17.0 → 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 (253) hide show
  1. package/lib/adapter/convertLoadSubsetOptions.d.ts +2 -0
  2. package/lib/adapter/convertLoadSubsetOptions.d.ts.map +1 -1
  3. package/lib/adapter/convertLoadSubsetOptions.js +158 -191
  4. package/lib/adapter/convertLoadSubsetOptions.js.map +1 -1
  5. package/lib/adapter/createFoundryOntologyBackendAdapter.d.ts +17 -15
  6. package/lib/adapter/createFoundryOntologyBackendAdapter.d.ts.map +1 -1
  7. package/lib/adapter/createFoundryOntologyBackendAdapter.js +288 -400
  8. package/lib/adapter/createFoundryOntologyBackendAdapter.js.map +1 -1
  9. package/lib/adapter/foundryActionValidation.d.ts +17 -15
  10. package/lib/adapter/foundryActionValidation.d.ts.map +1 -1
  11. package/lib/adapter/foundryActionValidation.js +206 -305
  12. package/lib/adapter/foundryActionValidation.js.map +1 -1
  13. package/lib/adapter/foundryCodec.d.ts +10 -9
  14. package/lib/adapter/foundryCodec.d.ts.map +1 -1
  15. package/lib/adapter/foundryCodec.js +180 -287
  16. package/lib/adapter/foundryCodec.js.map +1 -1
  17. package/lib/adapter/foundryMediaId.d.ts +6 -4
  18. package/lib/adapter/foundryMediaId.d.ts.map +1 -1
  19. package/lib/adapter/foundryMediaId.js +31 -32
  20. package/lib/adapter/foundryMediaId.js.map +1 -1
  21. package/lib/adapter/index.d.ts +4 -4
  22. package/lib/adapter/index.js +4 -4
  23. package/lib/adapter/objectCollectionOptions.d.ts +17 -18
  24. package/lib/adapter/objectCollectionOptions.d.ts.map +1 -1
  25. package/lib/adapter/objectCollectionOptions.js +417 -538
  26. package/lib/adapter/objectCollectionOptions.js.map +1 -1
  27. package/lib/config/applyAttachmentConstraintOverrides.d.ts +6 -4
  28. package/lib/config/applyAttachmentConstraintOverrides.d.ts.map +1 -1
  29. package/lib/config/applyAttachmentConstraintOverrides.js +45 -49
  30. package/lib/config/applyAttachmentConstraintOverrides.js.map +1 -1
  31. package/lib/config/index.d.ts +17 -15
  32. package/lib/config/index.d.ts.map +1 -1
  33. package/lib/config/index.js +56 -81
  34. package/lib/config/index.js.map +1 -1
  35. package/lib/connection.d.ts +28 -26
  36. package/lib/connection.d.ts.map +1 -1
  37. package/lib/connection.js +183 -215
  38. package/lib/connection.js.map +1 -1
  39. package/lib/index.d.ts +9 -5
  40. package/lib/index.js +9 -5
  41. package/lib/installation/createFoundryBackendInstallation.d.ts +21 -20
  42. package/lib/installation/createFoundryBackendInstallation.d.ts.map +1 -1
  43. package/lib/installation/createFoundryBackendInstallation.js +52 -62
  44. package/lib/installation/createFoundryBackendInstallation.js.map +1 -1
  45. package/lib/installation/index.d.ts +2 -2
  46. package/lib/installation/index.js +2 -2
  47. package/lib/meta/actionTypeCollectionOptions.d.ts +8 -6
  48. package/lib/meta/actionTypeCollectionOptions.d.ts.map +1 -1
  49. package/lib/meta/actionTypeCollectionOptions.js +49 -49
  50. package/lib/meta/actionTypeCollectionOptions.js.map +1 -1
  51. package/lib/meta/convertActionTypeLoadSubsetOptions.d.ts +3 -1
  52. package/lib/meta/convertActionTypeLoadSubsetOptions.d.ts.map +1 -1
  53. package/lib/meta/convertActionTypeLoadSubsetOptions.js +69 -75
  54. package/lib/meta/convertActionTypeLoadSubsetOptions.js.map +1 -1
  55. package/lib/meta/convertMetaActionType.d.ts +7 -5
  56. package/lib/meta/convertMetaActionType.d.ts.map +1 -1
  57. package/lib/meta/convertMetaActionType.js +458 -581
  58. package/lib/meta/convertMetaActionType.js.map +1 -1
  59. package/lib/meta/convertMetaLinkType.d.ts +4 -2
  60. package/lib/meta/convertMetaLinkType.d.ts.map +1 -1
  61. package/lib/meta/convertMetaLinkType.js +37 -43
  62. package/lib/meta/convertMetaLinkType.js.map +1 -1
  63. package/lib/meta/convertMetaObjectType.d.ts +4 -2
  64. package/lib/meta/convertMetaObjectType.d.ts.map +1 -1
  65. package/lib/meta/convertMetaObjectType.js +34 -35
  66. package/lib/meta/convertMetaObjectType.js.map +1 -1
  67. package/lib/meta/convertMetaQueryFunctionType.d.ts +4 -2
  68. package/lib/meta/convertMetaQueryFunctionType.d.ts.map +1 -1
  69. package/lib/meta/convertMetaQueryFunctionType.js +113 -94
  70. package/lib/meta/convertMetaQueryFunctionType.js.map +1 -1
  71. package/lib/meta/convertMetaTypeDef.d.ts +4 -2
  72. package/lib/meta/convertMetaTypeDef.d.ts.map +1 -1
  73. package/lib/meta/convertMetaTypeDef.js +178 -176
  74. package/lib/meta/convertMetaTypeDef.js.map +1 -1
  75. package/lib/meta/convertMetaValueType.d.ts +4 -2
  76. package/lib/meta/convertMetaValueType.d.ts.map +1 -1
  77. package/lib/meta/convertMetaValueType.js +11 -7
  78. package/lib/meta/convertMetaValueType.js.map +1 -1
  79. package/lib/meta/convertOmsActionParameterMetadata.d.ts +4 -2
  80. package/lib/meta/convertOmsActionParameterMetadata.d.ts.map +1 -1
  81. package/lib/meta/convertOmsActionParameterMetadata.js +107 -163
  82. package/lib/meta/convertOmsActionParameterMetadata.js.map +1 -1
  83. package/lib/meta/createFoundryMetaOntologyBackendAdapter.d.ts +7 -5
  84. package/lib/meta/createFoundryMetaOntologyBackendAdapter.d.ts.map +1 -1
  85. package/lib/meta/createFoundryMetaOntologyBackendAdapter.js +39 -41
  86. package/lib/meta/createFoundryMetaOntologyBackendAdapter.js.map +1 -1
  87. package/lib/meta/entityCollectionOptions.d.ts +12 -11
  88. package/lib/meta/entityCollectionOptions.d.ts.map +1 -1
  89. package/lib/meta/entityCollectionOptions.js +59 -62
  90. package/lib/meta/entityCollectionOptions.js.map +1 -1
  91. package/lib/meta/foundryAttachmentMetadata.d.ts +2 -0
  92. package/lib/meta/foundryAttachmentMetadata.d.ts.map +1 -1
  93. package/lib/meta/foundryAttachmentMetadata.js +13 -14
  94. package/lib/meta/foundryAttachmentMetadata.js.map +1 -1
  95. package/lib/meta/index.d.ts +5 -5
  96. package/lib/meta/index.js +5 -5
  97. package/lib/meta/loadActionTypeOmsMetadata.d.ts +5 -4
  98. package/lib/meta/loadActionTypeOmsMetadata.d.ts.map +1 -1
  99. package/lib/meta/loadActionTypeOmsMetadata.js +102 -139
  100. package/lib/meta/loadActionTypeOmsMetadata.js.map +1 -1
  101. package/lib/meta/queryFunctionTypeCollectionOptions.d.ts +7 -5
  102. package/lib/meta/queryFunctionTypeCollectionOptions.d.ts.map +1 -1
  103. package/lib/meta/queryFunctionTypeCollectionOptions.js +47 -49
  104. package/lib/meta/queryFunctionTypeCollectionOptions.js.map +1 -1
  105. package/lib/stagedWrites.d.ts +26 -24
  106. package/lib/stagedWrites.d.ts.map +1 -1
  107. package/lib/stagedWrites.js +55 -66
  108. package/lib/stagedWrites.js.map +1 -1
  109. package/lib/users/createFoundryAdminOntologyRoute.d.ts +47 -17366
  110. package/lib/users/createFoundryAdminOntologyRoute.d.ts.map +1 -1
  111. package/lib/users/createFoundryAdminOntologyRoute.js +29 -37
  112. package/lib/users/createFoundryAdminOntologyRoute.js.map +1 -1
  113. package/lib/users/createFoundryUsersIntegration.d.ts +6 -4
  114. package/lib/users/createFoundryUsersIntegration.d.ts.map +1 -1
  115. package/lib/users/createFoundryUsersIntegration.js +28 -30
  116. package/lib/users/createFoundryUsersIntegration.js.map +1 -1
  117. package/lib/users/foundryUser.d.ts +4 -2
  118. package/lib/users/foundryUser.d.ts.map +1 -1
  119. package/lib/users/foundryUser.js +69 -74
  120. package/lib/users/foundryUser.js.map +1 -1
  121. package/lib/users/index.d.ts +5 -5
  122. package/lib/users/index.js +5 -5
  123. package/lib/users/userCollectionOptions.d.ts +13 -12
  124. package/lib/users/userCollectionOptions.d.ts.map +1 -1
  125. package/lib/users/userCollectionOptions.js +82 -100
  126. package/lib/users/userCollectionOptions.js.map +1 -1
  127. package/lib/utils/AsyncIterable.d.ts +2 -0
  128. package/lib/utils/AsyncIterable.d.ts.map +1 -1
  129. package/lib/utils/AsyncIterable.js +26 -34
  130. package/lib/utils/AsyncIterable.js.map +1 -1
  131. package/lib/utils/actionTypeName.d.ts +2 -0
  132. package/lib/utils/actionTypeName.d.ts.map +1 -1
  133. package/lib/utils/actionTypeName.js +9 -7
  134. package/lib/utils/actionTypeName.js.map +1 -1
  135. package/lib/utils/chunk.d.ts +2 -0
  136. package/lib/utils/chunk.d.ts.map +1 -1
  137. package/lib/utils/chunk.js +8 -6
  138. package/lib/utils/chunk.js.map +1 -1
  139. package/package.json +21 -27
  140. package/lib/adapter/convertLoadSubsetOptions.test.d.ts +0 -2
  141. package/lib/adapter/convertLoadSubsetOptions.test.d.ts.map +0 -1
  142. package/lib/adapter/convertLoadSubsetOptions.test.js +0 -213
  143. package/lib/adapter/convertLoadSubsetOptions.test.js.map +0 -1
  144. package/lib/adapter/createFoundryOntologyBackendAdapter.test.d.ts +0 -2
  145. package/lib/adapter/createFoundryOntologyBackendAdapter.test.d.ts.map +0 -1
  146. package/lib/adapter/createFoundryOntologyBackendAdapter.test.js +0 -628
  147. package/lib/adapter/createFoundryOntologyBackendAdapter.test.js.map +0 -1
  148. package/lib/adapter/foundryActionValidation.test.d.ts +0 -2
  149. package/lib/adapter/foundryActionValidation.test.d.ts.map +0 -1
  150. package/lib/adapter/foundryActionValidation.test.js +0 -243
  151. package/lib/adapter/foundryActionValidation.test.js.map +0 -1
  152. package/lib/adapter/foundryCodec.test.d.ts +0 -2
  153. package/lib/adapter/foundryCodec.test.d.ts.map +0 -1
  154. package/lib/adapter/foundryCodec.test.js +0 -137
  155. package/lib/adapter/foundryCodec.test.js.map +0 -1
  156. package/lib/adapter/foundryMediaId.test.d.ts +0 -2
  157. package/lib/adapter/foundryMediaId.test.d.ts.map +0 -1
  158. package/lib/adapter/foundryMediaId.test.js +0 -23
  159. package/lib/adapter/foundryMediaId.test.js.map +0 -1
  160. package/lib/adapter/index.d.ts.map +0 -1
  161. package/lib/adapter/index.js.map +0 -1
  162. package/lib/adapter/objectCollectionOptions.test.d.ts +0 -2
  163. package/lib/adapter/objectCollectionOptions.test.d.ts.map +0 -1
  164. package/lib/adapter/objectCollectionOptions.test.js +0 -1289
  165. package/lib/adapter/objectCollectionOptions.test.js.map +0 -1
  166. package/lib/config/connectionPull.test.d.ts +0 -2
  167. package/lib/config/connectionPull.test.d.ts.map +0 -1
  168. package/lib/config/connectionPull.test.js +0 -151
  169. package/lib/config/connectionPull.test.js.map +0 -1
  170. package/lib/config/index.test.d.ts +0 -2
  171. package/lib/config/index.test.d.ts.map +0 -1
  172. package/lib/config/index.test.js +0 -182
  173. package/lib/config/index.test.js.map +0 -1
  174. package/lib/connection.test.d.ts +0 -2
  175. package/lib/connection.test.d.ts.map +0 -1
  176. package/lib/connection.test.js +0 -162
  177. package/lib/connection.test.js.map +0 -1
  178. package/lib/index.d.ts.map +0 -1
  179. package/lib/index.js.map +0 -1
  180. package/lib/installation/createFoundryBackendInstallation.test.d.ts +0 -2
  181. package/lib/installation/createFoundryBackendInstallation.test.d.ts.map +0 -1
  182. package/lib/installation/createFoundryBackendInstallation.test.js +0 -83
  183. package/lib/installation/createFoundryBackendInstallation.test.js.map +0 -1
  184. package/lib/installation/index.d.ts.map +0 -1
  185. package/lib/installation/index.js.map +0 -1
  186. package/lib/meta/actionTypeCollectionOptions.test.d.ts +0 -2
  187. package/lib/meta/actionTypeCollectionOptions.test.d.ts.map +0 -1
  188. package/lib/meta/actionTypeCollectionOptions.test.js +0 -24
  189. package/lib/meta/actionTypeCollectionOptions.test.js.map +0 -1
  190. package/lib/meta/actionTypeIdQuery.test.d.ts +0 -2
  191. package/lib/meta/actionTypeIdQuery.test.d.ts.map +0 -1
  192. package/lib/meta/actionTypeIdQuery.test.js +0 -131
  193. package/lib/meta/actionTypeIdQuery.test.js.map +0 -1
  194. package/lib/meta/convertActionTypeLoadSubsetOptions.test.d.ts +0 -2
  195. package/lib/meta/convertActionTypeLoadSubsetOptions.test.d.ts.map +0 -1
  196. package/lib/meta/convertActionTypeLoadSubsetOptions.test.js +0 -138
  197. package/lib/meta/convertActionTypeLoadSubsetOptions.test.js.map +0 -1
  198. package/lib/meta/convertMetaActionType.test.d.ts +0 -2
  199. package/lib/meta/convertMetaActionType.test.d.ts.map +0 -1
  200. package/lib/meta/convertMetaActionType.test.js +0 -1565
  201. package/lib/meta/convertMetaActionType.test.js.map +0 -1
  202. package/lib/meta/convertMetaLinkType.test.d.ts +0 -2
  203. package/lib/meta/convertMetaLinkType.test.d.ts.map +0 -1
  204. package/lib/meta/convertMetaLinkType.test.js +0 -110
  205. package/lib/meta/convertMetaLinkType.test.js.map +0 -1
  206. package/lib/meta/convertMetaObjectType.test.d.ts +0 -2
  207. package/lib/meta/convertMetaObjectType.test.d.ts.map +0 -1
  208. package/lib/meta/convertMetaObjectType.test.js +0 -250
  209. package/lib/meta/convertMetaObjectType.test.js.map +0 -1
  210. package/lib/meta/convertMetaQueryFunctionType.test.d.ts +0 -2
  211. package/lib/meta/convertMetaQueryFunctionType.test.d.ts.map +0 -1
  212. package/lib/meta/convertMetaQueryFunctionType.test.js +0 -134
  213. package/lib/meta/convertMetaQueryFunctionType.test.js.map +0 -1
  214. package/lib/meta/createFoundryMetaOntologyBackendAdapter.test.d.ts +0 -2
  215. package/lib/meta/createFoundryMetaOntologyBackendAdapter.test.d.ts.map +0 -1
  216. package/lib/meta/createFoundryMetaOntologyBackendAdapter.test.js +0 -49
  217. package/lib/meta/createFoundryMetaOntologyBackendAdapter.test.js.map +0 -1
  218. package/lib/meta/entityCollectionOptions.test.d.ts +0 -2
  219. package/lib/meta/entityCollectionOptions.test.d.ts.map +0 -1
  220. package/lib/meta/entityCollectionOptions.test.js +0 -98
  221. package/lib/meta/entityCollectionOptions.test.js.map +0 -1
  222. package/lib/meta/index.d.ts.map +0 -1
  223. package/lib/meta/index.js.map +0 -1
  224. package/lib/meta/loadActionTypeOmsMetadata.test.d.ts +0 -2
  225. package/lib/meta/loadActionTypeOmsMetadata.test.d.ts.map +0 -1
  226. package/lib/meta/loadActionTypeOmsMetadata.test.js +0 -218
  227. package/lib/meta/loadActionTypeOmsMetadata.test.js.map +0 -1
  228. package/lib/stagedWrites.test.d.ts +0 -2
  229. package/lib/stagedWrites.test.d.ts.map +0 -1
  230. package/lib/stagedWrites.test.js +0 -71
  231. package/lib/stagedWrites.test.js.map +0 -1
  232. package/lib/users/createFoundryUsersIntegration.test.d.ts +0 -2
  233. package/lib/users/createFoundryUsersIntegration.test.d.ts.map +0 -1
  234. package/lib/users/createFoundryUsersIntegration.test.js +0 -32
  235. package/lib/users/createFoundryUsersIntegration.test.js.map +0 -1
  236. package/lib/users/foundryUser.test.d.ts +0 -2
  237. package/lib/users/foundryUser.test.d.ts.map +0 -1
  238. package/lib/users/foundryUser.test.js +0 -45
  239. package/lib/users/foundryUser.test.js.map +0 -1
  240. package/lib/users/index.d.ts.map +0 -1
  241. package/lib/users/index.js.map +0 -1
  242. package/lib/users/userCollectionOptions.test.d.ts +0 -2
  243. package/lib/users/userCollectionOptions.test.d.ts.map +0 -1
  244. package/lib/users/userCollectionOptions.test.js +0 -18
  245. package/lib/users/userCollectionOptions.test.js.map +0 -1
  246. package/lib/utils/AsyncIterable.test.d.ts +0 -2
  247. package/lib/utils/AsyncIterable.test.d.ts.map +0 -1
  248. package/lib/utils/AsyncIterable.test.js +0 -17
  249. package/lib/utils/AsyncIterable.test.js.map +0 -1
  250. package/lib/utils/actionTypeName.test.d.ts +0 -2
  251. package/lib/utils/actionTypeName.test.d.ts.map +0 -1
  252. package/lib/utils/actionTypeName.test.js +0 -30
  253. package/lib/utils/actionTypeName.test.js.map +0 -1
@@ -1,1289 +0,0 @@
1
- import { o } from "@party-stack/ontology";
2
- import { eq, gt, IR } from "@tanstack/db";
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
- const mockState = vi.hoisted(() => ({
5
- getEditsHistory: vi.fn(),
6
- getObject: vi.fn(),
7
- search: vi.fn(),
8
- getObjectSetWatcherManager: vi.fn(),
9
- unsubscribe: vi.fn(),
10
- subscribeCallback: undefined,
11
- }));
12
- vi.mock("@osdk/foundry.ontologies", () => ({
13
- OntologyObjectsV2: {
14
- get: mockState.getObject,
15
- search: mockState.search,
16
- },
17
- ObjectTypesV2: {
18
- getEditsHistory: mockState.getEditsHistory,
19
- },
20
- }));
21
- vi.mock("@party-stack/foundry-object-set-watcher", () => ({
22
- getObjectSetWatcherManager: mockState.getObjectSetWatcherManager,
23
- }));
24
- import { createFoundryCodec } from "./foundryCodec.js";
25
- import { objectCollectionOptions } from "./objectCollectionOptions.js";
26
- function createSyncHarness(initialObjects = [], opts = {}) {
27
- const syncedData = new Map(initialObjects.map((object) => [object.employeeId, object]));
28
- const writes = [];
29
- const collectionMetadata = opts.collectionMetadata ?? new Map();
30
- const transactions = [];
31
- let pendingTransaction;
32
- const begin = vi.fn(() => {
33
- if (pendingTransaction)
34
- throw new Error("A sync transaction is already active.");
35
- pendingTransaction = {
36
- writes: [],
37
- collectionMetadata: new Map(),
38
- };
39
- });
40
- const commit = vi.fn(() => {
41
- if (!pendingTransaction)
42
- throw new Error("No sync transaction is active.");
43
- for (const [key, value] of pendingTransaction.collectionMetadata) {
44
- collectionMetadata.set(key, value);
45
- }
46
- transactions.push(pendingTransaction);
47
- pendingTransaction = undefined;
48
- return opts.commitReceipt?.() ?? true;
49
- });
50
- const truncate = vi.fn(() => {
51
- if (!pendingTransaction) {
52
- throw new Error("No sync transaction is active.");
53
- }
54
- syncedData.clear();
55
- });
56
- const { sync: syncConfig, utils } = objectCollectionOptions({
57
- client: {
58
- baseUrl: "https://example.com",
59
- fetch: opts.fetch ?? vi.fn(),
60
- ontologyRid: "ri.ontology.main",
61
- tokenProvider: () => Promise.resolve("token"),
62
- },
63
- objectType: "Employee",
64
- primaryKeyProperty: "employeeId",
65
- selectedProperties: [
66
- "employeeId",
67
- "name",
68
- "status",
69
- "priority",
70
- ],
71
- live: opts.live,
72
- decodeObject: opts.decodeObject,
73
- decodeEditObject: opts.decodeEditObject,
74
- });
75
- const handle = syncConfig.sync({
76
- begin,
77
- collection: {
78
- get: (key) => syncedData.get(key),
79
- has: (key) => syncedData.has(key),
80
- *keys() {
81
- yield* syncedData.keys();
82
- },
83
- syncedData,
84
- },
85
- commit,
86
- markError: vi.fn(),
87
- markReady: vi.fn(),
88
- metadata: {
89
- collection: {
90
- get: (key) => pendingTransaction?.collectionMetadata.get(key) ?? collectionMetadata.get(key),
91
- set: (key, value) => {
92
- if (!pendingTransaction)
93
- throw new Error("No sync transaction is active.");
94
- pendingTransaction.collectionMetadata.set(key, value);
95
- },
96
- delete: vi.fn(),
97
- list: () => [...collectionMetadata].map(([key, value]) => ({ key, value })),
98
- },
99
- row: {
100
- get: vi.fn(),
101
- set: vi.fn(),
102
- delete: vi.fn(),
103
- },
104
- },
105
- truncate,
106
- write: (mutation) => {
107
- if (!pendingTransaction)
108
- throw new Error("No sync transaction is active.");
109
- const key = mutation.type === "delete"
110
- ? mutation.key
111
- : mutation.value
112
- .employeeId;
113
- const observedWrite = { type: mutation.type, primaryKey: key };
114
- writes.push(observedWrite);
115
- pendingTransaction.writes.push(observedWrite);
116
- if (mutation.type === "delete") {
117
- syncedData.delete(key);
118
- }
119
- else {
120
- const value = mutation.value;
121
- syncedData.set(key, value);
122
- }
123
- },
124
- });
125
- return {
126
- begin,
127
- cleanup: handle.cleanup,
128
- collectionMetadata,
129
- commit,
130
- loadSubset: handle.loadSubset,
131
- syncedData,
132
- transactions,
133
- truncate,
134
- utils,
135
- writes,
136
- };
137
- }
138
- describe("objectCollectionOptions", () => {
139
- beforeEach(() => {
140
- mockState.getEditsHistory.mockReset();
141
- mockState.getObject.mockReset();
142
- mockState.search.mockReset();
143
- mockState.getObjectSetWatcherManager.mockReset();
144
- mockState.unsubscribe.mockReset();
145
- mockState.subscribeCallback = undefined;
146
- mockState.getObjectSetWatcherManager.mockReturnValue({
147
- subscribe: (_objectSet, callback) => {
148
- mockState.subscribeCallback = callback;
149
- return () => {
150
- mockState.subscribeCallback = undefined;
151
- mockState.unsubscribe();
152
- };
153
- },
154
- });
155
- });
156
- it("constructs and cleans up the object-set watcher by default", () => {
157
- const harness = createSyncHarness();
158
- expect(mockState.getObjectSetWatcherManager).toHaveBeenCalledTimes(1);
159
- harness.cleanup();
160
- expect(mockState.unsubscribe).toHaveBeenCalledTimes(1);
161
- });
162
- it("loads subsets without constructing an object-set watcher when live is false", async () => {
163
- mockState.search.mockResolvedValue({
164
- data: [{ employeeId: 1, name: "Ada" }],
165
- nextPageToken: undefined,
166
- });
167
- const harness = createSyncHarness([], { live: false });
168
- expect(mockState.getObjectSetWatcherManager).not.toHaveBeenCalled();
169
- await harness.loadSubset({});
170
- expect(harness.syncedData.get(1)).toEqual({
171
- employeeId: 1,
172
- name: "Ada",
173
- });
174
- harness.cleanup();
175
- expect(mockState.unsubscribe).not.toHaveBeenCalled();
176
- });
177
- it("supports bounded edit-history observation without a watcher when live is false", async () => {
178
- mockState.getEditsHistory.mockResolvedValue({
179
- data: [
180
- {
181
- objectPrimaryKey: { employeeId: 2 },
182
- operationId: "op-non-live",
183
- actionTypeRid: "action-1",
184
- userId: "user-1",
185
- timestamp: "2099-03-12T12:00:00.000Z",
186
- edit: {
187
- type: "createEdit",
188
- properties: {
189
- employeeId: 2,
190
- name: "Grace",
191
- },
192
- },
193
- },
194
- ],
195
- nextPageToken: undefined,
196
- });
197
- const harness = createSyncHarness([], { live: false });
198
- await expect(harness.utils.awaitOperationId("op-non-live")).resolves.toBeUndefined();
199
- expect(mockState.getObjectSetWatcherManager).not.toHaveBeenCalled();
200
- expect(harness.syncedData.get(2)).toEqual({
201
- employeeId: 2,
202
- name: "Grace",
203
- });
204
- harness.cleanup();
205
- });
206
- it("keeps identical abortable subset loads independent", async () => {
207
- const requests = [];
208
- const fetchImpl = vi.fn((_input, init) => {
209
- return new Promise((resolve, reject) => {
210
- const request = { resolve, reject };
211
- requests.push(request);
212
- const signal = init?.signal;
213
- signal?.addEventListener("abort", () => reject(signal.reason instanceof Error
214
- ? signal.reason
215
- : new DOMException("Request aborted.", "AbortError")), { once: true });
216
- });
217
- });
218
- mockState.search.mockImplementation((client) => client.fetch("https://example.com/search"));
219
- const harness = createSyncHarness([], { fetch: fetchImpl });
220
- const controllerA = new AbortController();
221
- const controllerB = new AbortController();
222
- const requestA = harness.loadSubset({
223
- limit: 1,
224
- signal: controllerA.signal,
225
- });
226
- const requestB = harness.loadSubset({
227
- limit: 1,
228
- signal: controllerB.signal,
229
- });
230
- expect(requestA).not.toBe(requestB);
231
- expect(mockState.search).toHaveBeenCalledTimes(2);
232
- controllerA.abort(new DOMException("Request A canceled.", "AbortError"));
233
- requests[1].resolve({
234
- data: [{ employeeId: "employee-b", name: "Request B" }],
235
- nextPageToken: undefined,
236
- });
237
- await expect(requestA).rejects.toMatchObject({ name: "AbortError" });
238
- await expect(requestB).resolves.toBeUndefined();
239
- expect(harness.syncedData.get("employee-b")).toMatchObject({
240
- name: "Request B",
241
- });
242
- harness.cleanup();
243
- });
244
- it("reconciles modify edits from history on watcher open", async () => {
245
- mockState.getEditsHistory.mockResolvedValue({
246
- data: [
247
- {
248
- objectPrimaryKey: { employeeId: 1 },
249
- operationId: "op-1",
250
- actionTypeRid: "action-1",
251
- userId: "user-1",
252
- timestamp: "2099-03-12T12:00:00.000Z",
253
- edit: {
254
- type: "modifyEdit",
255
- previousProperties: {},
256
- properties: {
257
- employeeId: 1,
258
- name: "Updated Employee",
259
- },
260
- },
261
- },
262
- ],
263
- nextPageToken: undefined,
264
- });
265
- const harness = createSyncHarness([
266
- {
267
- employeeId: 1,
268
- name: "Before",
269
- department: "Engineering",
270
- },
271
- ]);
272
- mockState.subscribeCallback?.({ type: "state", status: "open" });
273
- await vi.waitFor(() => {
274
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
275
- expect(harness.syncedData.get(1)).toEqual({
276
- employeeId: 1,
277
- name: "Updated Employee",
278
- department: "Engineering",
279
- });
280
- });
281
- expect(mockState.getObject).not.toHaveBeenCalled();
282
- expect(harness.begin.mock.calls).toEqual([
283
- [],
284
- [],
285
- ]);
286
- harness.cleanup();
287
- });
288
- it("applies delete edits from history", async () => {
289
- mockState.getEditsHistory.mockResolvedValue({
290
- data: [
291
- {
292
- objectPrimaryKey: { employeeId: 2 },
293
- operationId: "op-2",
294
- actionTypeRid: "action-1",
295
- userId: "user-1",
296
- timestamp: "2099-03-12T12:01:00.000Z",
297
- edit: {
298
- type: "deleteEdit",
299
- previousProperties: {},
300
- },
301
- },
302
- ],
303
- nextPageToken: undefined,
304
- });
305
- const harness = createSyncHarness([{ employeeId: 2, name: "Should Stay" }]);
306
- mockState.subscribeCallback?.({ type: "state", status: "open" });
307
- await vi.waitFor(() => {
308
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
309
- expect(harness.syncedData.has(2)).toBe(false);
310
- });
311
- expect(mockState.getObject).not.toHaveBeenCalled();
312
- harness.cleanup();
313
- });
314
- it("deletes objects hydrated after source sync starts", async () => {
315
- mockState.getEditsHistory.mockResolvedValue({
316
- data: [
317
- {
318
- objectPrimaryKey: {
319
- employeeId: 2,
320
- },
321
- operationId: "op-hydrated-delete",
322
- actionTypeRid: "action-1",
323
- userId: "user-1",
324
- timestamp: "2099-03-12T12:01:00.000Z",
325
- edit: {
326
- type: "deleteEdit",
327
- previousProperties: {},
328
- },
329
- },
330
- ],
331
- nextPageToken: undefined,
332
- });
333
- const harness = createSyncHarness();
334
- // Persisted on-demand hydration happens after the source sync closure
335
- // captures its initial state.
336
- harness.syncedData.set(2, {
337
- employeeId: 2,
338
- name: "Persisted stale object",
339
- });
340
- mockState.subscribeCallback?.({
341
- type: "state",
342
- status: "open",
343
- });
344
- await vi.waitFor(() => {
345
- expect(harness.syncedData.has(2)).toBe(false);
346
- });
347
- expect(harness.writes).toContainEqual({
348
- type: "delete",
349
- primaryKey: 2,
350
- });
351
- harness.cleanup();
352
- });
353
- it("treats watcher REMOVED events as a history catch-up signal", async () => {
354
- mockState.getEditsHistory.mockResolvedValue({
355
- data: [
356
- {
357
- objectPrimaryKey: { employeeId: 5 },
358
- operationId: "op-5",
359
- actionTypeRid: "action-1",
360
- userId: "user-1",
361
- timestamp: "2099-03-12T12:00:00.000Z",
362
- edit: {
363
- type: "createEdit",
364
- properties: {
365
- employeeId: 5,
366
- name: "Employee Five",
367
- },
368
- },
369
- },
370
- ],
371
- nextPageToken: undefined,
372
- });
373
- mockState.getEditsHistory.mockResolvedValueOnce({
374
- data: [
375
- {
376
- objectPrimaryKey: { employeeId: 5 },
377
- operationId: "op-5",
378
- actionTypeRid: "action-1",
379
- userId: "user-1",
380
- timestamp: "2099-03-12T12:00:00.000Z",
381
- edit: {
382
- type: "createEdit",
383
- properties: {
384
- employeeId: 5,
385
- name: "Employee Five",
386
- },
387
- },
388
- },
389
- ],
390
- nextPageToken: undefined,
391
- });
392
- mockState.getEditsHistory.mockResolvedValueOnce({
393
- data: [
394
- {
395
- objectPrimaryKey: { employeeId: 5 },
396
- operationId: "op-6",
397
- actionTypeRid: "action-1",
398
- userId: "user-1",
399
- timestamp: "2099-03-12T12:01:00.000Z",
400
- edit: {
401
- type: "deleteEdit",
402
- previousProperties: {},
403
- },
404
- },
405
- ],
406
- nextPageToken: undefined,
407
- });
408
- const harness = createSyncHarness();
409
- mockState.subscribeCallback?.({ type: "state", status: "open" });
410
- await vi.waitFor(() => {
411
- expect(harness.syncedData.has(5)).toBe(true);
412
- });
413
- mockState.subscribeCallback?.({
414
- type: "change",
415
- updates: [
416
- {
417
- type: "object",
418
- state: "REMOVED",
419
- object: { employeeId: 5 },
420
- },
421
- ],
422
- });
423
- await vi.waitFor(() => {
424
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2);
425
- expect(harness.syncedData.has(5)).toBe(false);
426
- expect(harness.writes).toContainEqual({ type: "delete", primaryKey: 5 });
427
- });
428
- harness.cleanup();
429
- });
430
- it("dedupes inclusive timestamp results across catch-up runs", async () => {
431
- mockState.getEditsHistory
432
- .mockResolvedValueOnce({
433
- data: [
434
- {
435
- objectPrimaryKey: { employeeId: 1 },
436
- operationId: "op-1",
437
- actionTypeRid: "action-1",
438
- userId: "user-1",
439
- timestamp: "2099-03-12T12:00:00.000Z",
440
- edit: {
441
- type: "modifyEdit",
442
- previousProperties: {},
443
- properties: {
444
- employeeId: 1,
445
- name: "Employee One",
446
- },
447
- },
448
- },
449
- ],
450
- nextPageToken: undefined,
451
- })
452
- .mockResolvedValueOnce({
453
- data: [
454
- {
455
- objectPrimaryKey: { employeeId: 1 },
456
- operationId: "op-1",
457
- actionTypeRid: "action-1",
458
- userId: "user-1",
459
- timestamp: "2099-03-12T12:00:00.000Z",
460
- edit: {
461
- type: "modifyEdit",
462
- previousProperties: {},
463
- properties: {
464
- employeeId: 1,
465
- name: "Employee One",
466
- },
467
- },
468
- },
469
- {
470
- objectPrimaryKey: { employeeId: 2 },
471
- operationId: "op-2",
472
- actionTypeRid: "action-1",
473
- userId: "user-1",
474
- timestamp: "2099-03-12T12:00:00.000Z",
475
- edit: {
476
- type: "modifyEdit",
477
- previousProperties: {},
478
- properties: {
479
- employeeId: 2,
480
- name: "Employee Two",
481
- },
482
- },
483
- },
484
- ],
485
- nextPageToken: undefined,
486
- });
487
- const harness = createSyncHarness();
488
- mockState.subscribeCallback?.({ type: "state", status: "open" });
489
- mockState.subscribeCallback?.({
490
- type: "change",
491
- updates: [
492
- {
493
- type: "object",
494
- state: "ADDED_OR_UPDATED",
495
- object: { employeeId: 2, name: "Employee Two" },
496
- },
497
- ],
498
- });
499
- await vi.waitFor(() => {
500
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2);
501
- expect(harness.syncedData.get(1)).toEqual({
502
- employeeId: 1,
503
- name: "Employee One",
504
- });
505
- expect(harness.syncedData.get(2)).toEqual({
506
- employeeId: 2,
507
- name: "Employee Two",
508
- });
509
- });
510
- expect(mockState.getObject).not.toHaveBeenCalled();
511
- harness.cleanup();
512
- });
513
- it("restores a durable same-timestamp cursor and catches downtime edits once", async () => {
514
- const timestamp = "2099-03-12T12:00:00.000Z";
515
- const firstEntry = {
516
- objectPrimaryKey: { employeeId: 1 },
517
- operationId: "op-1",
518
- actionTypeRid: "action-1",
519
- userId: "user-1",
520
- timestamp,
521
- edit: {
522
- type: "modifyEdit",
523
- previousProperties: {},
524
- properties: {
525
- employeeId: 1,
526
- name: "Employee One",
527
- },
528
- },
529
- };
530
- const downtimeEntry = {
531
- objectPrimaryKey: { employeeId: 2 },
532
- operationId: "op-2",
533
- actionTypeRid: "action-1",
534
- userId: "user-1",
535
- timestamp,
536
- edit: {
537
- type: "modifyEdit",
538
- previousProperties: {},
539
- properties: {
540
- employeeId: 2,
541
- name: "Employee Two",
542
- },
543
- },
544
- };
545
- const collectionMetadata = new Map();
546
- mockState.getEditsHistory.mockResolvedValue({
547
- data: [firstEntry],
548
- nextPageToken: undefined,
549
- });
550
- const first = createSyncHarness([], { collectionMetadata });
551
- mockState.subscribeCallback?.({ type: "state", status: "open" });
552
- await vi.waitFor(() => {
553
- expect(first.syncedData.get(1)).toMatchObject({ name: "Employee One" });
554
- });
555
- const persistedCursor = [...collectionMetadata.values()][0];
556
- expect(persistedCursor).toMatchObject({
557
- version: 1,
558
- timestamp,
559
- });
560
- expect(persistedCursor.seenEntryKeysAtTimestamp).toHaveLength(1);
561
- expect(first.transactions.at(-1)).toMatchObject({
562
- writes: [{ type: "insert", primaryKey: 1 }],
563
- });
564
- expect(first.transactions.at(-1)?.collectionMetadata.size).toBe(1);
565
- const reloadedObjects = [...first.syncedData.values()];
566
- first.cleanup();
567
- mockState.getEditsHistory.mockReset();
568
- mockState.getEditsHistory.mockResolvedValue({
569
- data: [firstEntry, downtimeEntry],
570
- nextPageToken: undefined,
571
- });
572
- const second = createSyncHarness(reloadedObjects, { collectionMetadata });
573
- mockState.subscribeCallback?.({ type: "state", status: "open" });
574
- await vi.waitFor(() => {
575
- expect(second.syncedData.get(2)).toMatchObject({ name: "Employee Two" });
576
- });
577
- expect(second.writes).toEqual([{ type: "insert", primaryKey: 2 }]);
578
- expect(mockState.getEditsHistory.mock.calls[0]?.[3]).toMatchObject({
579
- filters: {
580
- type: "timestampFilter",
581
- startTime: timestamp,
582
- },
583
- });
584
- expect([...collectionMetadata.values()][0].seenEntryKeysAtTimestamp).toHaveLength(2);
585
- second.cleanup();
586
- });
587
- it("persists delete and cursor progress for unloaded rows", async () => {
588
- const timestamp = "2099-03-12T12:02:00.000Z";
589
- mockState.getEditsHistory.mockResolvedValue({
590
- data: [
591
- {
592
- objectPrimaryKey: { employeeId: 404 },
593
- operationId: "op-delete-missing",
594
- actionTypeRid: "action-1",
595
- userId: "user-1",
596
- timestamp,
597
- edit: {
598
- type: "deleteEdit",
599
- previousProperties: {},
600
- },
601
- },
602
- ],
603
- nextPageToken: undefined,
604
- });
605
- const harness = createSyncHarness();
606
- mockState.subscribeCallback?.({ type: "state", status: "open" });
607
- await vi.waitFor(() => {
608
- expect(harness.transactions).toHaveLength(2);
609
- });
610
- expect(harness.writes).toEqual([
611
- {
612
- type: "delete",
613
- primaryKey: 404,
614
- },
615
- ]);
616
- expect(harness.transactions.at(-1)?.writes).toEqual([
617
- {
618
- type: "delete",
619
- primaryKey: 404,
620
- },
621
- ]);
622
- expect(harness.transactions.at(-1)?.collectionMetadata.size).toBe(1);
623
- expect([...harness.collectionMetadata.values()][0]).toMatchObject({
624
- version: 1,
625
- timestamp,
626
- });
627
- harness.cleanup();
628
- });
629
- it("decodes create edits with null property sentinels from edit history", async () => {
630
- mockState.getEditsHistory.mockResolvedValue({
631
- data: [
632
- {
633
- objectPrimaryKey: { employeeId: 3 },
634
- operationId: "op-1",
635
- actionTypeRid: "action-1",
636
- userId: "user-1",
637
- timestamp: "2099-03-12T12:00:00.000Z",
638
- edit: {
639
- type: "createEdit",
640
- properties: {
641
- employeeId: 3,
642
- nickname: "NullPropertyValue{}",
643
- name: "Employee Three",
644
- createdAt: "2099-03-12T12:00:00.000Z",
645
- },
646
- },
647
- },
648
- ],
649
- nextPageToken: undefined,
650
- });
651
- const codec = createFoundryCodec({
652
- types: [],
653
- objectTypes: [
654
- {
655
- name: "Employee",
656
- displayName: "Employee",
657
- pluralDisplayName: "Employees",
658
- primaryKey: "employeeId",
659
- properties: [
660
- { name: "employeeId", displayName: "Employee ID", type: o.integer({}) },
661
- { name: "name", displayName: "Name", type: o.string({}) },
662
- {
663
- name: "nickname",
664
- displayName: "Nickname",
665
- type: o.optional({ type: o.string({}) }),
666
- },
667
- ],
668
- },
669
- ],
670
- linkTypes: [],
671
- actionTypes: [],
672
- queryFunctionTypes: [],
673
- });
674
- const harness = createSyncHarness([], {
675
- decodeEditObject: (object) => codec.decodeEditObject("Employee", object),
676
- });
677
- mockState.subscribeCallback?.({ type: "state", status: "open" });
678
- await vi.waitFor(() => {
679
- expect(harness.syncedData.get(3)).toEqual({
680
- employeeId: 3,
681
- name: "Employee Three",
682
- nickname: undefined,
683
- createdAt: "2099-03-12T12:00:00.000Z",
684
- });
685
- });
686
- expect(mockState.getObject).not.toHaveBeenCalled();
687
- harness.cleanup();
688
- });
689
- it("decodes attachment property wrappers from edit history", async () => {
690
- mockState.getEditsHistory.mockResolvedValue({
691
- data: [
692
- {
693
- objectPrimaryKey: { employeeId: 7 },
694
- operationId: "op-7",
695
- actionTypeRid: "action-1",
696
- userId: "user-1",
697
- timestamp: "2099-03-12T12:00:00.000Z",
698
- edit: {
699
- type: "createEdit",
700
- properties: {
701
- employeeId: 7,
702
- attachments: [
703
- {
704
- type: "attachment",
705
- attachment: "ri.attachments.main.attachment.7",
706
- },
707
- ],
708
- name: "Employee Seven",
709
- },
710
- },
711
- },
712
- ],
713
- nextPageToken: undefined,
714
- });
715
- const codec = createFoundryCodec({
716
- types: [],
717
- objectTypes: [
718
- {
719
- name: "Employee",
720
- displayName: "Employee",
721
- pluralDisplayName: "Employees",
722
- primaryKey: "employeeId",
723
- properties: [
724
- { name: "employeeId", displayName: "Employee ID", type: o.integer({}) },
725
- { name: "name", displayName: "Name", type: o.string({}) },
726
- {
727
- name: "attachments",
728
- displayName: "Attachments",
729
- type: o.list({
730
- elementType: o.attachment({
731
- meta: {
732
- foundry: {
733
- kind: "attachment",
734
- },
735
- },
736
- }),
737
- }),
738
- },
739
- ],
740
- },
741
- ],
742
- linkTypes: [],
743
- actionTypes: [],
744
- queryFunctionTypes: [],
745
- });
746
- const harness = createSyncHarness([], {
747
- decodeObject: (object) => codec.decodeObject("Employee", object),
748
- decodeEditObject: (object) => codec.decodeEditObject("Employee", object),
749
- });
750
- mockState.subscribeCallback?.({ type: "state", status: "open" });
751
- await vi.waitFor(() => {
752
- expect(harness.syncedData.get(7)).toEqual({
753
- employeeId: 7,
754
- name: "Employee Seven",
755
- attachments: [{ id: "ri.attachments.main.attachment.7" }],
756
- });
757
- });
758
- harness.cleanup();
759
- });
760
- it("decodes media-reference wrappers from edit history using the property schema", async () => {
761
- const mediaReference = {
762
- mimeType: "image/png",
763
- reference: {
764
- type: "mediaSetViewItem",
765
- mediaSetViewItem: {
766
- mediaSetRid: "ri.mio.main.media-set.1",
767
- mediaSetViewRid: "ri.mio.main.view.1",
768
- mediaItemRid: "ri.mio.main.media-item.1",
769
- },
770
- },
771
- };
772
- mockState.getEditsHistory.mockResolvedValue({
773
- data: [
774
- {
775
- objectPrimaryKey: { employeeId: 8 },
776
- operationId: "op-8",
777
- actionTypeRid: "action-1",
778
- userId: "user-1",
779
- timestamp: "2099-03-12T12:00:00.000Z",
780
- edit: {
781
- type: "createEdit",
782
- properties: {
783
- employeeId: 8,
784
- logoMedia: {
785
- type: "mediaReference",
786
- mediaReference,
787
- },
788
- },
789
- },
790
- },
791
- ],
792
- nextPageToken: undefined,
793
- });
794
- const codec = createFoundryCodec({
795
- types: [],
796
- objectTypes: [
797
- {
798
- name: "Employee",
799
- displayName: "Employee",
800
- pluralDisplayName: "Employees",
801
- primaryKey: "employeeId",
802
- properties: [
803
- { name: "employeeId", displayName: "Employee ID", type: o.integer({}) },
804
- {
805
- name: "logoMedia",
806
- displayName: "Logo media",
807
- type: o.optional({
808
- type: o.attachment({
809
- meta: {
810
- foundry: {
811
- kind: "media",
812
- },
813
- },
814
- }),
815
- }),
816
- },
817
- ],
818
- },
819
- ],
820
- linkTypes: [],
821
- actionTypes: [],
822
- queryFunctionTypes: [],
823
- });
824
- const harness = createSyncHarness([], {
825
- decodeEditObject: (object) => codec.decodeEditObject("Employee", object),
826
- });
827
- mockState.subscribeCallback?.({ type: "state", status: "open" });
828
- await vi.waitFor(() => {
829
- expect(harness.syncedData.get(8)).toEqual({
830
- employeeId: 8,
831
- logoMedia: {
832
- id: [
833
- "ri.mio.main.media-set.1",
834
- "ri.mio.main.view.1",
835
- "ri.mio.main.media-item.1",
836
- ].join(":"),
837
- type: "image/png",
838
- },
839
- });
840
- });
841
- harness.cleanup();
842
- });
843
- it("decodes GeoPointPropertyValue strings from edit history", async () => {
844
- mockState.getEditsHistory.mockResolvedValue({
845
- data: [
846
- {
847
- objectPrimaryKey: { employeeId: 5 },
848
- operationId: "op-1",
849
- actionTypeRid: "action-1",
850
- userId: "user-1",
851
- timestamp: "2099-03-12T12:00:00.000Z",
852
- edit: {
853
- type: "createEdit",
854
- properties: {
855
- employeeId: 5,
856
- name: "Employee Five",
857
- location: "GeoPointPropertyValue{latitude: 40.375786, longitude: -74.11144}",
858
- },
859
- },
860
- },
861
- ],
862
- nextPageToken: undefined,
863
- });
864
- const codec = createFoundryCodec({
865
- types: [],
866
- objectTypes: [
867
- {
868
- name: "Employee",
869
- displayName: "Employee",
870
- pluralDisplayName: "Employees",
871
- primaryKey: "employeeId",
872
- properties: [
873
- { name: "employeeId", displayName: "Employee ID", type: o.integer({}) },
874
- { name: "name", displayName: "Name", type: o.string({}) },
875
- { name: "location", displayName: "Location", type: o.geopoint({}) },
876
- ],
877
- },
878
- ],
879
- linkTypes: [],
880
- actionTypes: [],
881
- queryFunctionTypes: [],
882
- });
883
- const harness = createSyncHarness([], {
884
- decodeEditObject: (object) => codec.decodeEditObject("Employee", object),
885
- });
886
- mockState.subscribeCallback?.({ type: "state", status: "open" });
887
- await vi.waitFor(() => {
888
- expect(harness.syncedData.get(5)).toEqual({
889
- employeeId: 5,
890
- name: "Employee Five",
891
- location: { lat: 40.375786, lon: -74.11144 },
892
- });
893
- });
894
- expect(mockState.getObject).not.toHaveBeenCalled();
895
- harness.cleanup();
896
- });
897
- it("unwraps tagged primary key values from edit history", async () => {
898
- mockState.getEditsHistory.mockResolvedValue({
899
- data: [
900
- {
901
- objectPrimaryKey: {
902
- employeeId: {
903
- type: "stringValue",
904
- value: "employee-4",
905
- },
906
- },
907
- operationId: "op-1",
908
- actionTypeRid: "action-1",
909
- userId: "user-1",
910
- timestamp: "2099-03-12T12:00:00.000Z",
911
- edit: {
912
- type: "createEdit",
913
- properties: {
914
- employeeId: {
915
- type: "stringValue",
916
- value: "employee-4",
917
- },
918
- name: "Employee Four",
919
- },
920
- },
921
- },
922
- ],
923
- nextPageToken: undefined,
924
- });
925
- const codec = createFoundryCodec({
926
- types: [],
927
- objectTypes: [
928
- {
929
- name: "Employee",
930
- displayName: "Employee",
931
- pluralDisplayName: "Employees",
932
- primaryKey: "employeeId",
933
- properties: [
934
- { name: "employeeId", displayName: "Employee ID", type: o.string({}) },
935
- { name: "name", displayName: "Name", type: o.string({}) },
936
- ],
937
- },
938
- ],
939
- linkTypes: [],
940
- actionTypes: [],
941
- queryFunctionTypes: [],
942
- });
943
- const harness = createSyncHarness([], {
944
- decodeEditObject: (object) => codec.decodeEditObject("Employee", object),
945
- });
946
- mockState.subscribeCallback?.({ type: "state", status: "open" });
947
- await vi.waitFor(() => {
948
- expect(harness.syncedData.get("employee-4")).toEqual({
949
- employeeId: "employee-4",
950
- name: "Employee Four",
951
- });
952
- });
953
- expect(mockState.getObject).not.toHaveBeenCalled();
954
- harness.cleanup();
955
- });
956
- it("triggers history catch-up on ADDED_OR_UPDATED watcher events", async () => {
957
- mockState.getEditsHistory
958
- .mockResolvedValueOnce({
959
- data: [],
960
- nextPageToken: undefined,
961
- })
962
- .mockResolvedValueOnce({
963
- data: [
964
- {
965
- objectPrimaryKey: { employeeId: 10 },
966
- operationId: "op-10",
967
- actionTypeRid: "action-1",
968
- userId: "user-1",
969
- timestamp: "2099-03-12T12:05:00.000Z",
970
- edit: {
971
- type: "createEdit",
972
- properties: {
973
- employeeId: 10,
974
- name: "New Employee",
975
- },
976
- },
977
- },
978
- ],
979
- nextPageToken: undefined,
980
- });
981
- const harness = createSyncHarness();
982
- mockState.subscribeCallback?.({ type: "state", status: "open" });
983
- await vi.waitFor(() => {
984
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
985
- });
986
- mockState.subscribeCallback?.({
987
- type: "change",
988
- updates: [
989
- {
990
- type: "object",
991
- state: "ADDED_OR_UPDATED",
992
- object: { employeeId: 10, name: "New Employee" },
993
- },
994
- ],
995
- });
996
- await vi.waitFor(() => {
997
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2);
998
- expect(harness.syncedData.get(10)).toEqual({
999
- employeeId: 10,
1000
- name: "New Employee",
1001
- });
1002
- });
1003
- harness.cleanup();
1004
- });
1005
- it("falls back to direct websocket updates when edit history is unavailable", async () => {
1006
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
1007
- mockState.getEditsHistory.mockRejectedValue(new Error("Edit history is not enabled"));
1008
- const harness = createSyncHarness();
1009
- const baselineCheckpoint = JSON.parse(JSON.stringify([...harness.collectionMetadata.values()][0]));
1010
- mockState.subscribeCallback?.({
1011
- type: "change",
1012
- updates: [
1013
- {
1014
- type: "object",
1015
- state: "ADDED_OR_UPDATED",
1016
- object: { employeeId: 12, name: "Direct Employee" },
1017
- },
1018
- ],
1019
- });
1020
- await vi.waitFor(() => {
1021
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
1022
- expect(harness.syncedData.get(12)).toEqual({
1023
- employeeId: 12,
1024
- name: "Direct Employee",
1025
- });
1026
- });
1027
- mockState.subscribeCallback?.({
1028
- type: "change",
1029
- updates: [
1030
- {
1031
- type: "object",
1032
- state: "REMOVED",
1033
- object: { employeeId: 12 },
1034
- },
1035
- ],
1036
- });
1037
- await vi.waitFor(() => {
1038
- expect(harness.syncedData.has(12)).toBe(false);
1039
- expect(harness.writes).toContainEqual({ type: "delete", primaryKey: 12 });
1040
- });
1041
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
1042
- expect([...harness.collectionMetadata.values()][0]).toEqual(baselineCheckpoint);
1043
- expect(harness.transactions.at(-1)?.collectionMetadata.size).toBe(0);
1044
- harness.cleanup();
1045
- warnSpy.mockRestore();
1046
- });
1047
- it("fully reconciles after websocket reconnect when edit history is unavailable", async () => {
1048
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
1049
- mockState.getEditsHistory.mockRejectedValue(new Error("Edit history is not enabled"));
1050
- mockState.search.mockResolvedValue({
1051
- data: [],
1052
- nextPageToken: undefined,
1053
- });
1054
- const harness = createSyncHarness();
1055
- mockState.subscribeCallback?.({
1056
- type: "state",
1057
- status: "open",
1058
- });
1059
- await vi.waitFor(() => {
1060
- expect(mockState.search).toHaveBeenCalledOnce();
1061
- });
1062
- // Simulate stale persisted state becoming visible while the watcher is
1063
- // disconnected and therefore unable to report its deletion.
1064
- harness.syncedData.set(30, {
1065
- employeeId: 30,
1066
- name: "Stale Employee",
1067
- });
1068
- mockState.subscribeCallback?.({
1069
- type: "state",
1070
- status: "open",
1071
- });
1072
- await vi.waitFor(() => {
1073
- expect(mockState.search).toHaveBeenCalledTimes(2);
1074
- expect(harness.syncedData.has(30)).toBe(false);
1075
- });
1076
- expect(harness.truncate).toHaveBeenCalled();
1077
- harness.cleanup();
1078
- warnSpy.mockRestore();
1079
- });
1080
- it("does not checkpoint partially fetched history when falling back", async () => {
1081
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
1082
- mockState.getEditsHistory
1083
- .mockResolvedValueOnce({
1084
- data: [
1085
- {
1086
- objectPrimaryKey: { employeeId: 20 },
1087
- operationId: "op-history-20",
1088
- actionTypeRid: "action-1",
1089
- userId: "user-1",
1090
- timestamp: "2099-03-12T12:10:00.000Z",
1091
- edit: {
1092
- type: "createEdit",
1093
- properties: {
1094
- employeeId: 20,
1095
- name: "Uncommitted History Employee",
1096
- },
1097
- },
1098
- },
1099
- ],
1100
- nextPageToken: "next",
1101
- })
1102
- .mockRejectedValueOnce(new Error("Edit history pagination failed"));
1103
- const harness = createSyncHarness();
1104
- const baselineCheckpoint = JSON.parse(JSON.stringify([...harness.collectionMetadata.values()][0]));
1105
- mockState.subscribeCallback?.({
1106
- type: "change",
1107
- updates: [
1108
- {
1109
- type: "object",
1110
- state: "ADDED_OR_UPDATED",
1111
- object: { employeeId: 21, name: "Direct Employee" },
1112
- },
1113
- ],
1114
- });
1115
- await vi.waitFor(() => {
1116
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(2);
1117
- expect(harness.syncedData.get(21)).toMatchObject({ name: "Direct Employee" });
1118
- });
1119
- expect(harness.syncedData.has(20)).toBe(false);
1120
- expect(harness.writes).toEqual([{ type: "insert", primaryKey: 21 }]);
1121
- expect([...harness.collectionMetadata.values()][0]).toEqual(baselineCheckpoint);
1122
- harness.cleanup();
1123
- warnSpy.mockRestore();
1124
- });
1125
- it("returns immediately when awaiting an operation before sync has started", async () => {
1126
- const { utils } = objectCollectionOptions({
1127
- client: {
1128
- baseUrl: "https://example.com",
1129
- fetch: vi.fn(),
1130
- ontologyRid: "ri.ontology.main",
1131
- tokenProvider: () => Promise.resolve("token"),
1132
- },
1133
- objectType: "Employee",
1134
- primaryKeyProperty: "employeeId",
1135
- selectedProperties: ["employeeId", "name"],
1136
- });
1137
- await expect(utils.awaitOperationId("op-before-sync")).resolves.toBeUndefined();
1138
- expect(mockState.getEditsHistory).not.toHaveBeenCalled();
1139
- });
1140
- it("resolves awaitOperationId after direct websocket sync observes an update", async () => {
1141
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
1142
- mockState.getEditsHistory.mockRejectedValue(new Error("Edit history is not enabled"));
1143
- let resolveCommit;
1144
- const commitApplied = new Promise((resolve) => {
1145
- resolveCommit = resolve;
1146
- });
1147
- const harness = createSyncHarness([], {
1148
- commitReceipt: () => commitApplied,
1149
- });
1150
- const operationPromise = harness.utils.awaitOperationId("op-12");
1151
- await vi.waitFor(() => {
1152
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
1153
- });
1154
- mockState.subscribeCallback?.({
1155
- type: "change",
1156
- updates: [
1157
- {
1158
- type: "object",
1159
- state: "ADDED_OR_UPDATED",
1160
- object: { employeeId: 13, name: "Direct Operation Employee" },
1161
- },
1162
- ],
1163
- });
1164
- await expect(operationPromise).resolves.toBeUndefined();
1165
- expect(harness.syncedData.get(13)).toEqual({
1166
- employeeId: 13,
1167
- name: "Direct Operation Employee",
1168
- });
1169
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
1170
- resolveCommit();
1171
- await commitApplied;
1172
- harness.cleanup();
1173
- warnSpy.mockRestore();
1174
- });
1175
- it("awaitOperationId resolves after the matching edit is observed", async () => {
1176
- mockState.getEditsHistory.mockResolvedValue({
1177
- data: [
1178
- {
1179
- objectPrimaryKey: { employeeId: 11 },
1180
- operationId: "op-11",
1181
- actionTypeRid: "action-1",
1182
- userId: "user-1",
1183
- timestamp: "2099-03-12T12:06:00.000Z",
1184
- edit: {
1185
- type: "createEdit",
1186
- properties: {
1187
- employeeId: 11,
1188
- name: "Operation Eleven",
1189
- },
1190
- },
1191
- },
1192
- ],
1193
- nextPageToken: undefined,
1194
- });
1195
- let resolveCommit;
1196
- const commitApplied = new Promise((resolve) => {
1197
- resolveCommit = resolve;
1198
- });
1199
- const harness = createSyncHarness([], {
1200
- commitReceipt: () => commitApplied,
1201
- });
1202
- await expect(harness.utils.awaitOperationId("op-11")).resolves.toBeUndefined();
1203
- expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1);
1204
- expect(harness.syncedData.get(11)).toEqual({
1205
- employeeId: 11,
1206
- name: "Operation Eleven",
1207
- });
1208
- resolveCommit();
1209
- await commitApplied;
1210
- harness.cleanup();
1211
- });
1212
- it("combines the subset where clause with cursor.whereFrom", async () => {
1213
- mockState.search.mockResolvedValue({
1214
- data: [{ employeeId: 2, status: "open", priority: 6 }],
1215
- nextPageToken: undefined,
1216
- });
1217
- const harness = createSyncHarness();
1218
- await harness.loadSubset({
1219
- where: eq(new IR.PropRef(["status"]), "open"),
1220
- cursor: {
1221
- whereFrom: gt(new IR.PropRef(["priority"]), 5),
1222
- whereCurrent: eq(new IR.PropRef(["priority"]), 5),
1223
- },
1224
- });
1225
- const searchOptions = mockState.search.mock.calls[0]?.[3];
1226
- expect(searchOptions).toMatchObject({
1227
- where: {
1228
- type: "and",
1229
- value: [
1230
- {
1231
- type: "eq",
1232
- propertyIdentifier: { type: "property", apiName: "status" },
1233
- value: "open",
1234
- },
1235
- {
1236
- type: "gt",
1237
- propertyIdentifier: { type: "property", apiName: "priority" },
1238
- value: 5,
1239
- },
1240
- ],
1241
- },
1242
- });
1243
- expect(searchOptions).toMatchObject({
1244
- select: [
1245
- "employeeId",
1246
- "name",
1247
- "status",
1248
- "priority",
1249
- ],
1250
- });
1251
- expect(searchOptions?.selectV2).toEqual([]);
1252
- expect(harness.syncedData.has(2)).toBe(true);
1253
- harness.cleanup();
1254
- });
1255
- it("over-fetches ordered subsets before applying an offset", async () => {
1256
- mockState.search.mockResolvedValue({
1257
- data: [
1258
- { employeeId: 1, priority: 1 },
1259
- { employeeId: 2, priority: 2 },
1260
- { employeeId: 3, priority: 3 },
1261
- { employeeId: 4, priority: 4 },
1262
- ],
1263
- nextPageToken: undefined,
1264
- });
1265
- const harness = createSyncHarness();
1266
- await harness.loadSubset({
1267
- orderBy: [
1268
- {
1269
- expression: new IR.PropRef(["priority"]),
1270
- compareOptions: {
1271
- direction: "asc",
1272
- nulls: "last",
1273
- },
1274
- },
1275
- ],
1276
- offset: 2,
1277
- limit: 2,
1278
- });
1279
- expect(mockState.search.mock.calls[0]?.[3]).toMatchObject({
1280
- pageSize: 4,
1281
- orderBy: {
1282
- fields: [{ field: "priority", direction: "asc" }],
1283
- },
1284
- });
1285
- expect([...harness.syncedData.keys()]).toEqual([3, 4]);
1286
- harness.cleanup();
1287
- });
1288
- });
1289
- //# sourceMappingURL=objectCollectionOptions.test.js.map