@nest-yalc-2/omnikernel-module 1.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.
Files changed (230) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +193 -0
  3. package/package.json +52 -0
  4. package/src/__tests__/omni-collection.entity.spec.ts +49 -0
  5. package/src/__tests__/omni-collection.service.spec.ts +83 -0
  6. package/src/__tests__/omni-document.entity.spec.ts +55 -0
  7. package/src/__tests__/omni-document.service.spec.ts +83 -0
  8. package/src/__tests__/omni-extension-projection-constraints.spec.ts +518 -0
  9. package/src/__tests__/omni-extension-projection.registration.spec.ts +946 -0
  10. package/src/__tests__/omni-extension-projection.service.spec.ts +806 -0
  11. package/src/__tests__/omni-external-ref.entity.spec.ts +36 -0
  12. package/src/__tests__/omni-external-ref.service.spec.ts +252 -0
  13. package/src/__tests__/omni-named.entity.spec.ts +33 -0
  14. package/src/__tests__/omni-record.entity.spec.ts +32 -0
  15. package/src/__tests__/omni-record.service.spec.ts +107 -0
  16. package/src/__tests__/omni-relation-semantics.spec.ts +102 -0
  17. package/src/__tests__/omni-relation.entity.spec.ts +38 -0
  18. package/src/__tests__/omni-scope-substrate.spec.ts +81 -0
  19. package/src/__tests__/omni-scoped.backend.spec.ts +115 -0
  20. package/src/__tests__/omni-scoped.service.spec.ts +354 -0
  21. package/src/__tests__/omnikernel.diagnostics.spec.ts +94 -0
  22. package/src/__tests__/omnikernel.module.spec.ts +40 -0
  23. package/src/__tests__/omnikernel.persistence.spec.ts +154 -0
  24. package/src/__tests__/omnikernel.public-api.spec.ts +238 -0
  25. package/src/__tests__/omnikernel.query.service.spec.ts +155 -0
  26. package/src/__tests__/tsconfig.json +6 -0
  27. package/src/base/omni-base.entity.d.ts +9 -0
  28. package/src/base/omni-base.entity.js +53 -0
  29. package/src/base/omni-base.entity.js.map +1 -0
  30. package/src/base/omni-base.entity.ts +41 -0
  31. package/src/base/omni-external-ref.entity.d.ts +13 -0
  32. package/src/base/omni-external-ref.entity.js +72 -0
  33. package/src/base/omni-external-ref.entity.js.map +1 -0
  34. package/src/base/omni-external-ref.entity.ts +53 -0
  35. package/src/base/omni-named.entity.d.ts +6 -0
  36. package/src/base/omni-named.entity.js +35 -0
  37. package/src/base/omni-named.entity.js.map +1 -0
  38. package/src/base/omni-named.entity.ts +16 -0
  39. package/src/base/omni-record.entity.d.ts +13 -0
  40. package/src/base/omni-record.entity.js +71 -0
  41. package/src/base/omni-record.entity.js.map +1 -0
  42. package/src/base/omni-record.entity.ts +51 -0
  43. package/src/base/omni-relation.entity.d.ts +15 -0
  44. package/src/base/omni-relation.entity.js +91 -0
  45. package/src/base/omni-relation.entity.js.map +1 -0
  46. package/src/base/omni-relation.entity.ts +72 -0
  47. package/src/index.d.ts +49 -0
  48. package/src/index.js +66 -0
  49. package/src/index.js.map +1 -0
  50. package/src/index.ts +49 -0
  51. package/src/omni-collection-kind.enum.d.ts +4 -0
  52. package/src/omni-collection-kind.enum.js +13 -0
  53. package/src/omni-collection-kind.enum.js.map +1 -0
  54. package/src/omni-collection-kind.enum.ts +10 -0
  55. package/src/omni-collection.backend.d.ts +2 -0
  56. package/src/omni-collection.backend.js +15 -0
  57. package/src/omni-collection.backend.js.map +1 -0
  58. package/src/omni-collection.backend.ts +16 -0
  59. package/src/omni-collection.dto.d.ts +32 -0
  60. package/src/omni-collection.dto.js +184 -0
  61. package/src/omni-collection.dto.js.map +1 -0
  62. package/src/omni-collection.dto.ts +170 -0
  63. package/src/omni-collection.entity.d.ts +7 -0
  64. package/src/omni-collection.entity.js +41 -0
  65. package/src/omni-collection.entity.js.map +1 -0
  66. package/src/omni-collection.entity.ts +21 -0
  67. package/src/omni-collection.service.d.ts +14 -0
  68. package/src/omni-collection.service.js +24 -0
  69. package/src/omni-collection.service.js.map +1 -0
  70. package/src/omni-collection.service.ts +75 -0
  71. package/src/omni-document-kind.enum.d.ts +6 -0
  72. package/src/omni-document-kind.enum.js +15 -0
  73. package/src/omni-document-kind.enum.js.map +1 -0
  74. package/src/omni-document-kind.enum.ts +12 -0
  75. package/src/omni-document.backend.d.ts +2 -0
  76. package/src/omni-document.backend.js +15 -0
  77. package/src/omni-document.backend.js.map +1 -0
  78. package/src/omni-document.backend.ts +16 -0
  79. package/src/omni-document.dto.d.ts +35 -0
  80. package/src/omni-document.dto.js +213 -0
  81. package/src/omni-document.dto.js.map +1 -0
  82. package/src/omni-document.dto.ts +198 -0
  83. package/src/omni-document.entity.d.ts +10 -0
  84. package/src/omni-document.entity.js +53 -0
  85. package/src/omni-document.entity.js.map +1 -0
  86. package/src/omni-document.entity.ts +30 -0
  87. package/src/omni-document.service.d.ts +14 -0
  88. package/src/omni-document.service.js +24 -0
  89. package/src/omni-document.service.js.map +1 -0
  90. package/src/omni-document.service.ts +75 -0
  91. package/src/omni-dto.helpers.d.ts +1 -0
  92. package/src/omni-dto.helpers.js +10 -0
  93. package/src/omni-dto.helpers.js.map +1 -0
  94. package/src/omni-dto.helpers.ts +21 -0
  95. package/src/omni-extension-projection.definition.d.ts +17 -0
  96. package/src/omni-extension-projection.definition.js +160 -0
  97. package/src/omni-extension-projection.definition.js.map +1 -0
  98. package/src/omni-extension-projection.definition.ts +294 -0
  99. package/src/omni-extension-projection.resource.d.ts +42 -0
  100. package/src/omni-extension-projection.resource.js +127 -0
  101. package/src/omni-extension-projection.resource.js.map +1 -0
  102. package/src/omni-extension-projection.resource.ts +274 -0
  103. package/src/omni-extension-projection.service.d.ts +33 -0
  104. package/src/omni-extension-projection.service.js +332 -0
  105. package/src/omni-extension-projection.service.js.map +1 -0
  106. package/src/omni-extension-projection.service.ts +452 -0
  107. package/src/omni-external-ref-binding.validator.d.ts +14 -0
  108. package/src/omni-external-ref-binding.validator.js +34 -0
  109. package/src/omni-external-ref-binding.validator.js.map +1 -0
  110. package/src/omni-external-ref-binding.validator.ts +53 -0
  111. package/src/omni-external-ref-internal-type.enum.d.ts +5 -0
  112. package/src/omni-external-ref-internal-type.enum.js +14 -0
  113. package/src/omni-external-ref-internal-type.enum.js.map +1 -0
  114. package/src/omni-external-ref-internal-type.enum.ts +11 -0
  115. package/src/omni-external-ref.backend.d.ts +2 -0
  116. package/src/omni-external-ref.backend.js +19 -0
  117. package/src/omni-external-ref.backend.js.map +1 -0
  118. package/src/omni-external-ref.backend.ts +25 -0
  119. package/src/omni-external-ref.dto.d.ts +26 -0
  120. package/src/omni-external-ref.dto.js +140 -0
  121. package/src/omni-external-ref.dto.js.map +1 -0
  122. package/src/omni-external-ref.dto.ts +128 -0
  123. package/src/omni-external-ref.service.d.ts +33 -0
  124. package/src/omni-external-ref.service.js +133 -0
  125. package/src/omni-external-ref.service.js.map +1 -0
  126. package/src/omni-external-ref.service.ts +255 -0
  127. package/src/omni-migration.d.ts +28 -0
  128. package/src/omni-migration.js +239 -0
  129. package/src/omni-migration.js.map +1 -0
  130. package/src/omni-migration.ts +365 -0
  131. package/src/omni-named.backend.d.ts +2 -0
  132. package/src/omni-named.backend.js +13 -0
  133. package/src/omni-named.backend.js.map +1 -0
  134. package/src/omni-named.backend.ts +11 -0
  135. package/src/omni-named.dto.d.ts +19 -0
  136. package/src/omni-named.dto.js +94 -0
  137. package/src/omni-named.dto.js.map +1 -0
  138. package/src/omni-named.dto.ts +86 -0
  139. package/src/omni-projection.catalog.d.ts +49 -0
  140. package/src/omni-projection.catalog.js +177 -0
  141. package/src/omni-projection.catalog.js.map +1 -0
  142. package/src/omni-projection.catalog.ts +345 -0
  143. package/src/omni-projection.lifecycle.d.ts +20 -0
  144. package/src/omni-projection.lifecycle.js +3 -0
  145. package/src/omni-projection.lifecycle.js.map +1 -0
  146. package/src/omni-projection.lifecycle.ts +53 -0
  147. package/src/omni-record-status.enum.d.ts +5 -0
  148. package/src/omni-record-status.enum.js +14 -0
  149. package/src/omni-record-status.enum.js.map +1 -0
  150. package/src/omni-record-status.enum.ts +11 -0
  151. package/src/omni-record.backend.d.ts +2 -0
  152. package/src/omni-record.backend.js +13 -0
  153. package/src/omni-record.backend.js.map +1 -0
  154. package/src/omni-record.backend.ts +19 -0
  155. package/src/omni-record.dto.d.ts +29 -0
  156. package/src/omni-record.dto.js +169 -0
  157. package/src/omni-record.dto.js.map +1 -0
  158. package/src/omni-record.dto.ts +157 -0
  159. package/src/omni-record.service.d.ts +18 -0
  160. package/src/omni-record.service.js +45 -0
  161. package/src/omni-record.service.js.map +1 -0
  162. package/src/omni-record.service.ts +113 -0
  163. package/src/omni-relation-kind.contract.d.ts +9 -0
  164. package/src/omni-relation-kind.contract.js +33 -0
  165. package/src/omni-relation-kind.contract.js.map +1 -0
  166. package/src/omni-relation-kind.contract.ts +49 -0
  167. package/src/omni-relation-kind.enum.d.ts +6 -0
  168. package/src/omni-relation-kind.enum.js +15 -0
  169. package/src/omni-relation-kind.enum.js.map +1 -0
  170. package/src/omni-relation-kind.enum.ts +12 -0
  171. package/src/omni-relation-projection.definition.d.ts +25 -0
  172. package/src/omni-relation-projection.definition.js +83 -0
  173. package/src/omni-relation-projection.definition.js.map +1 -0
  174. package/src/omni-relation-projection.definition.ts +166 -0
  175. package/src/omni-relation-projection.resource.d.ts +51 -0
  176. package/src/omni-relation-projection.resource.js +203 -0
  177. package/src/omni-relation-projection.resource.js.map +1 -0
  178. package/src/omni-relation-projection.resource.ts +360 -0
  179. package/src/omni-relation-projection.service.d.ts +47 -0
  180. package/src/omni-relation-projection.service.js +385 -0
  181. package/src/omni-relation-projection.service.js.map +1 -0
  182. package/src/omni-relation-projection.service.ts +610 -0
  183. package/src/omni-relation-semantics.d.ts +11 -0
  184. package/src/omni-relation-semantics.js +34 -0
  185. package/src/omni-relation-semantics.js.map +1 -0
  186. package/src/omni-relation-semantics.ts +52 -0
  187. package/src/omni-relation-status.enum.d.ts +5 -0
  188. package/src/omni-relation-status.enum.js +14 -0
  189. package/src/omni-relation-status.enum.js.map +1 -0
  190. package/src/omni-relation-status.enum.ts +11 -0
  191. package/src/omni-relation.backend.d.ts +2 -0
  192. package/src/omni-relation.backend.js +17 -0
  193. package/src/omni-relation.backend.js.map +1 -0
  194. package/src/omni-relation.backend.ts +21 -0
  195. package/src/omni-relation.dto.d.ts +28 -0
  196. package/src/omni-relation.dto.js +153 -0
  197. package/src/omni-relation.dto.js.map +1 -0
  198. package/src/omni-relation.dto.ts +142 -0
  199. package/src/omni-relation.service.d.ts +19 -0
  200. package/src/omni-relation.service.js +76 -0
  201. package/src/omni-relation.service.js.map +1 -0
  202. package/src/omni-relation.service.ts +159 -0
  203. package/src/omni-scope.d.ts +33 -0
  204. package/src/omni-scope.js +91 -0
  205. package/src/omni-scope.js.map +1 -0
  206. package/src/omni-scope.ts +161 -0
  207. package/src/omni-scoped.backend.d.ts +21 -0
  208. package/src/omni-scoped.backend.js +47 -0
  209. package/src/omni-scoped.backend.js.map +1 -0
  210. package/src/omni-scoped.backend.ts +110 -0
  211. package/src/omni-scoped.repository.d.ts +10 -0
  212. package/src/omni-scoped.repository.js +26 -0
  213. package/src/omni-scoped.repository.js.map +1 -0
  214. package/src/omni-scoped.repository.ts +34 -0
  215. package/src/omni-scoped.service.d.ts +36 -0
  216. package/src/omni-scoped.service.js +171 -0
  217. package/src/omni-scoped.service.js.map +1 -0
  218. package/src/omni-scoped.service.ts +338 -0
  219. package/src/omnikernel.diagnostics.d.ts +16 -0
  220. package/src/omnikernel.diagnostics.js +50 -0
  221. package/src/omnikernel.diagnostics.js.map +1 -0
  222. package/src/omnikernel.diagnostics.ts +83 -0
  223. package/src/omnikernel.module.d.ts +5 -0
  224. package/src/omnikernel.module.js +87 -0
  225. package/src/omnikernel.module.js.map +1 -0
  226. package/src/omnikernel.module.ts +97 -0
  227. package/src/omnikernel.query.service.d.ts +17 -0
  228. package/src/omnikernel.query.service.js +85 -0
  229. package/src/omnikernel.query.service.js.map +1 -0
  230. package/src/omnikernel.query.service.ts +108 -0
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniBaseEntity } from '../base/omni-base.entity.js';
5
+ import {
6
+ OmniExternalRefEntity,
7
+ } from '../base/omni-external-ref.entity.js';
8
+
9
+ describe('OmniExternalRefEntity', () => {
10
+ it('extends OmniBaseEntity', () => {
11
+ expect(new OmniExternalRefEntity()).toBeInstanceOf(OmniBaseEntity);
12
+ });
13
+
14
+ it('registers external mapping columns', () => {
15
+ const metadata = getMetadataArgsStorage();
16
+ const table = metadata.tables.find(
17
+ (item) => item.target === OmniExternalRefEntity,
18
+ );
19
+ const entityColumns = metadata.columns
20
+ .filter((item) => item.target === OmniExternalRefEntity)
21
+ .map((item) => item.propertyName);
22
+
23
+ expect(table?.name).toBe('omni-external-ref');
24
+ expect(entityColumns).toEqual(
25
+ expect.arrayContaining([
26
+ 'internalType',
27
+ 'internalId',
28
+ 'provider',
29
+ 'account',
30
+ 'container',
31
+ 'externalId',
32
+ 'payload',
33
+ ]),
34
+ );
35
+ });
36
+ });
@@ -0,0 +1,252 @@
1
+ import { describe, expect, it, jest } from "@jest/globals";
2
+
3
+ import { OmniExternalRefService } from '../omni-external-ref.service.js';
4
+ import { OmniExternalRefEntity } from '../base/omni-external-ref.entity.js';
5
+ import { OmniExternalRefInternalType } from '../omni-external-ref-internal-type.enum.js';
6
+
7
+ const bindingValidator = {
8
+ assertTarget: jest.fn(async () => ({})),
9
+ };
10
+
11
+ const createRepositoryPair = () => {
12
+ const readRepository = {
13
+ target: OmniExternalRefEntity,
14
+ findOne: jest.fn(),
15
+ find: jest.fn(),
16
+ findOneOrFail: jest.fn(),
17
+ getId: jest.fn((entity: OmniExternalRefEntity) => ({ guid: entity.guid })),
18
+ };
19
+ const writeRepository = {
20
+ target: OmniExternalRefEntity,
21
+ create: jest.fn((input: unknown) => input),
22
+ insert: jest.fn(async () => ({
23
+ identifiers: [{ guid: "eeeeeeee-1111-1111-1111-111111111111" }],
24
+ })),
25
+ update: jest.fn(async () => ({ affected: 1 })),
26
+ };
27
+
28
+ return { readRepository, writeRepository };
29
+ };
30
+
31
+ describe("OmniExternalRefService", () => {
32
+ it('requires a binding validator instead of silently bypassing target validation', () => {
33
+ const { readRepository, writeRepository } = createRepositoryPair();
34
+
35
+ expect(
36
+ () =>
37
+ new OmniExternalRefService(
38
+ readRepository as never,
39
+ writeRepository as never,
40
+ 'hard',
41
+ undefined as never,
42
+ ),
43
+ ).toThrow('requires an OmniExternalRefBindingValidator');
44
+ });
45
+
46
+ it("looks up references by provider/external identity", async () => {
47
+ const { readRepository, writeRepository } = createRepositoryPair();
48
+ const service = new OmniExternalRefService(
49
+ readRepository as never,
50
+ writeRepository as never,
51
+ 'hard',
52
+ bindingValidator as never,
53
+ );
54
+
55
+ await service.findByExternalIdentity({
56
+ provider: "github",
57
+ externalId: "123",
58
+ account: "acme",
59
+ container: "kb",
60
+ });
61
+
62
+ expect(readRepository.findOne).toHaveBeenCalledWith({
63
+ where: {
64
+ scopeId: "default",
65
+ provider: "github",
66
+ externalId: "123",
67
+ account: "acme",
68
+ container: "kb",
69
+ },
70
+ });
71
+ });
72
+
73
+ it("returns all references for an internal record", async () => {
74
+ const { readRepository, writeRepository } = createRepositoryPair();
75
+ const service = new OmniExternalRefService(
76
+ readRepository as never,
77
+ writeRepository as never,
78
+ 'hard',
79
+ bindingValidator as never,
80
+ );
81
+
82
+ await service.findForInternalRecord(
83
+ OmniExternalRefInternalType.Document,
84
+ "doc-1",
85
+ "github",
86
+ );
87
+
88
+ expect(readRepository.find).toHaveBeenCalledWith({
89
+ where: {
90
+ scopeId: "default",
91
+ internalType: OmniExternalRefInternalType.Document,
92
+ internalId: "doc-1",
93
+ provider: "github",
94
+ },
95
+ order: {
96
+ createdAt: "ASC",
97
+ },
98
+ });
99
+ });
100
+
101
+ it("creates a new external ref when the identity does not exist", async () => {
102
+ const { readRepository, writeRepository } = createRepositoryPair();
103
+ readRepository.findOne.mockResolvedValue(null);
104
+ const service = new OmniExternalRefService(
105
+ readRepository as never,
106
+ writeRepository as never,
107
+ 'hard',
108
+ bindingValidator as never,
109
+ );
110
+ const createSpy = jest
111
+ .spyOn(service, "createEntity")
112
+ .mockResolvedValue({ guid: "created" } as OmniExternalRefEntity);
113
+
114
+ const result = await service.upsertExternalRef({
115
+ internalType: OmniExternalRefInternalType.Document,
116
+ internalId: "doc-1",
117
+ provider: "github",
118
+ externalId: "123",
119
+ });
120
+
121
+ expect(createSpy).toHaveBeenCalledWith(
122
+ expect.objectContaining({
123
+ internalType: OmniExternalRefInternalType.Document,
124
+ internalId: "doc-1",
125
+ }),
126
+ );
127
+ expect(result.guid).toBe("created");
128
+ });
129
+
130
+ it("validates record bindings without changing legacy non-record bindings", async () => {
131
+ const { readRepository, writeRepository } = createRepositoryPair();
132
+ readRepository.findOne.mockResolvedValue(null);
133
+ const validator = {
134
+ assertTarget: jest.fn(async () => ({})),
135
+ };
136
+ const service = new OmniExternalRefService(
137
+ readRepository as never,
138
+ writeRepository as never,
139
+ "hard",
140
+ validator as never,
141
+ );
142
+
143
+ await service.createEntity({
144
+ guid: "eeeeeeee-1111-1111-1111-111111111112",
145
+ provider: "example",
146
+ externalId: "record",
147
+ internalType: OmniExternalRefInternalType.Record,
148
+ internalId: "aaaaaaaa-1111-1111-1111-111111111111",
149
+ });
150
+ expect(validator.assertTarget).toHaveBeenCalledWith({
151
+ internalType: OmniExternalRefInternalType.Record,
152
+ internalId: "aaaaaaaa-1111-1111-1111-111111111111",
153
+ });
154
+
155
+ await service.createEntity({
156
+ guid: "eeeeeeee-1111-1111-1111-111111111113",
157
+ provider: "example",
158
+ externalId: "legacy-document",
159
+ internalType: OmniExternalRefInternalType.Document,
160
+ internalId: "aaaaaaaa-1111-1111-1111-111111111112",
161
+ });
162
+ expect(validator.assertTarget).toHaveBeenCalledTimes(1);
163
+ });
164
+
165
+ it("updates an existing external ref when the identity already exists", async () => {
166
+ const { readRepository, writeRepository } = createRepositoryPair();
167
+ readRepository.findOne.mockResolvedValue({
168
+ guid: "existing-guid",
169
+ internalType: OmniExternalRefInternalType.Document,
170
+ internalId: "doc-1",
171
+ });
172
+ const service = new OmniExternalRefService(
173
+ readRepository as never,
174
+ writeRepository as never,
175
+ 'hard',
176
+ bindingValidator as never,
177
+ );
178
+ const updateSpy = jest
179
+ .spyOn(service, "updateEntity")
180
+ .mockResolvedValue({ guid: "existing-guid" } as OmniExternalRefEntity);
181
+
182
+ const result = await service.upsertExternalRef({
183
+ internalType: OmniExternalRefInternalType.Document,
184
+ internalId: "doc-1",
185
+ provider: "github",
186
+ externalId: "123",
187
+ payload: { synced: true },
188
+ });
189
+
190
+ expect(updateSpy).toHaveBeenCalledWith(
191
+ { guid: "existing-guid" },
192
+ expect.objectContaining({
193
+ payload: { synced: true },
194
+ }),
195
+ );
196
+ expect(result.guid).toBe("existing-guid");
197
+ });
198
+
199
+ it("rejects blank provider or external identity during upsert", async () => {
200
+ const { readRepository, writeRepository } = createRepositoryPair();
201
+ const service = new OmniExternalRefService(
202
+ readRepository as never,
203
+ writeRepository as never,
204
+ 'hard',
205
+ bindingValidator as never,
206
+ );
207
+
208
+ await expect(
209
+ service.upsertExternalRef({
210
+ internalType: OmniExternalRefInternalType.Document,
211
+ internalId: "doc-1",
212
+ provider: "",
213
+ externalId: "123",
214
+ }),
215
+ ).rejects.toThrow("OmniExternalRef.provider is required");
216
+
217
+ await expect(
218
+ service.upsertExternalRef({
219
+ internalType: OmniExternalRefInternalType.Document,
220
+ internalId: "doc-1",
221
+ provider: "github",
222
+ externalId: " ",
223
+ }),
224
+ ).rejects.toThrow("OmniExternalRef.externalId is required");
225
+ });
226
+
227
+ it("syncs document references with the document internal type", async () => {
228
+ const { readRepository, writeRepository } = createRepositoryPair();
229
+ const service = new OmniExternalRefService(
230
+ readRepository as never,
231
+ writeRepository as never,
232
+ 'hard',
233
+ bindingValidator as never,
234
+ );
235
+ const upsertSpy = jest
236
+ .spyOn(service, "upsertExternalRef")
237
+ .mockResolvedValue({ guid: "doc-ref" } as OmniExternalRefEntity);
238
+
239
+ await service.syncDocumentReference("doc-1", {
240
+ provider: "github",
241
+ externalId: "123",
242
+ account: "acme",
243
+ });
244
+
245
+ expect(upsertSpy).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ internalType: OmniExternalRefInternalType.Document,
248
+ internalId: "doc-1",
249
+ }),
250
+ );
251
+ });
252
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniBaseEntity } from '../base/omni-base.entity.js';
5
+ import { OmniNamedEntity } from '../base/omni-named.entity.js';
6
+
7
+ describe('OmniNamedEntity', () => {
8
+ it('extends OmniBaseEntity', () => {
9
+ expect(new OmniNamedEntity()).toBeInstanceOf(OmniBaseEntity);
10
+ });
11
+
12
+ it('registers the expected table and columns', () => {
13
+ const metadata = getMetadataArgsStorage();
14
+ const table = metadata.tables.find(
15
+ (item) => item.target === OmniNamedEntity,
16
+ );
17
+ const baseColumns = metadata.columns
18
+ .filter((item) => item.target === OmniBaseEntity)
19
+ .map((item) => item.propertyName);
20
+ const entityColumns = metadata.columns
21
+ .filter((item) => item.target === OmniNamedEntity)
22
+ .map((item) => item.propertyName);
23
+
24
+ expect(table?.name).toBe('omni-named');
25
+ expect(baseColumns).toEqual(expect.arrayContaining(['guid']));
26
+ expect(metadata.columns.map((item) => item.propertyName)).toEqual(
27
+ expect.arrayContaining(['createdAt', 'updatedAt']),
28
+ );
29
+ expect(entityColumns).toEqual(
30
+ expect.arrayContaining(['externalId', 'title', 'slug']),
31
+ );
32
+ });
33
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniNamedEntity } from '../base/omni-named.entity.js';
5
+ import { OmniRecordEntity } from '../base/omni-record.entity.js';
6
+
7
+ describe('OmniRecordEntity', () => {
8
+ it('extends OmniNamedEntity', () => {
9
+ expect(new OmniRecordEntity()).toBeInstanceOf(OmniNamedEntity);
10
+ });
11
+
12
+ it('registers core columns and directional relations', () => {
13
+ const metadata = getMetadataArgsStorage();
14
+ const table = metadata.tables.find(
15
+ (item) => item.target === OmniRecordEntity,
16
+ );
17
+ const entityColumns = metadata.columns
18
+ .filter((item) => item.target === OmniRecordEntity)
19
+ .map((item) => item.propertyName);
20
+ const relations = metadata.relations
21
+ .filter((item) => item.target === OmniRecordEntity)
22
+ .map((item) => item.propertyName);
23
+
24
+ expect(table?.name).toBe('omni-record');
25
+ expect(entityColumns).toEqual(
26
+ expect.arrayContaining(['kind', 'status', 'payload']),
27
+ );
28
+ expect(relations).toEqual(
29
+ expect.arrayContaining(['outgoingRelations', 'incomingRelations']),
30
+ );
31
+ });
32
+ });
@@ -0,0 +1,107 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from '@jest/globals';
2
+ import { DataSource } from 'typeorm';
3
+
4
+ import { OmniNamedEntity } from '../base/omni-named.entity.js';
5
+ import { OmniRecordEntity } from '../base/omni-record.entity.js';
6
+ import { OmniRelationEntity } from '../base/omni-relation.entity.js';
7
+ import { OmniExternalRefEntity } from '../base/omni-external-ref.entity.js';
8
+ import { OmniRecordStatus } from '../omni-record-status.enum.js';
9
+ import { OmniRecordService } from '../omni-record.service.js';
10
+
11
+ const scope = {
12
+ scopeId: 'scope-record-guard',
13
+ cacheKey: (key: string) => `scope-record-guard:${key}`,
14
+ };
15
+ const reservedKind = 'registered_extension';
16
+ const ownerGuid = '10000000-0000-4000-8000-000000000301';
17
+ const normalGuid = '10000000-0000-4000-8000-000000000302';
18
+
19
+ describe('OmniRecordService extension owner guard', () => {
20
+ let dataSource: DataSource;
21
+ let service: InstanceType<typeof OmniRecordService>;
22
+
23
+ beforeEach(async () => {
24
+ dataSource = new DataSource({
25
+ type: 'sqlite',
26
+ database: ':memory:',
27
+ dropSchema: true,
28
+ synchronize: true,
29
+ entities: [
30
+ OmniNamedEntity,
31
+ OmniRecordEntity,
32
+ OmniRelationEntity,
33
+ OmniExternalRefEntity,
34
+ ],
35
+ });
36
+ await dataSource.initialize();
37
+ service = new OmniRecordService(
38
+ dataSource.getRepository(OmniRecordEntity) as never,
39
+ scope,
40
+ 'tombstone',
41
+ [reservedKind],
42
+ );
43
+ await dataSource.getRepository(OmniRecordEntity).save({
44
+ scopeId: scope.scopeId,
45
+ guid: ownerGuid,
46
+ title: 'Extension-owned record',
47
+ kind: reservedKind,
48
+ status: OmniRecordStatus.Active,
49
+ payloadSchemaId: 'registered.extension.v1',
50
+ payloadSchemaVersion: 1,
51
+ });
52
+ });
53
+
54
+ afterEach(async () => {
55
+ await dataSource.destroy();
56
+ });
57
+
58
+ it('rejects generic creation, mutation, promotion, and deletion of a reserved extension owner', async () => {
59
+ await expect(
60
+ service.createEntity({
61
+ guid: '10000000-0000-4000-8000-000000000303',
62
+ title: 'Attempted extension owner',
63
+ kind: reservedKind,
64
+ status: OmniRecordStatus.Active,
65
+ }),
66
+ ).rejects.toThrow('owned by a registered extension projection');
67
+
68
+ await service.createEntity({
69
+ guid: normalGuid,
70
+ title: 'Normal record',
71
+ kind: 'normal_record',
72
+ status: OmniRecordStatus.Active,
73
+ });
74
+ await expect(
75
+ service.updateEntity(
76
+ { guid: normalGuid },
77
+ { kind: reservedKind },
78
+ ),
79
+ ).rejects.toThrow('owned by a registered extension projection');
80
+ await expect(
81
+ service.updateEntity(
82
+ { guid: ownerGuid },
83
+ { kind: 'normal_record' },
84
+ ),
85
+ ).rejects.toThrow('owned by a registered extension projection');
86
+ await expect(service.deleteEntity({ guid: ownerGuid })).rejects.toThrow(
87
+ 'owned by a registered extension projection',
88
+ );
89
+
90
+ expect(
91
+ await dataSource.getRepository(OmniRecordEntity).findOneByOrFail({
92
+ scopeId: scope.scopeId,
93
+ guid: ownerGuid,
94
+ }),
95
+ ).toMatchObject({
96
+ kind: reservedKind,
97
+ payloadSchemaId: 'registered.extension.v1',
98
+ payloadSchemaVersion: 1,
99
+ });
100
+ expect(
101
+ await dataSource.getRepository(OmniRecordEntity).findOneByOrFail({
102
+ scopeId: scope.scopeId,
103
+ guid: normalGuid,
104
+ }),
105
+ ).toMatchObject({ kind: 'normal_record' });
106
+ });
107
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+
3
+ import { OmniCollectionKind } from '../omni-collection-kind.enum.js';
4
+ import { OmniDocumentKind } from '../omni-document-kind.enum.js';
5
+ import { OmniRelationKind } from '../omni-relation-kind.enum.js';
6
+ import {
7
+ OMNI_COLLECTION_MEMBERSHIP_RELATION_KIND,
8
+ isAllowedOmniRelation,
9
+ isCanonicalCollectionMembershipRelation,
10
+ isOmniCollectionRecordKind,
11
+ isOmniDocumentRecordKind,
12
+ } from '../omni-relation-semantics.js';
13
+
14
+ describe('Omni relation semantics', () => {
15
+ it('treats collection contains as the canonical collection membership edge', () => {
16
+ expect(OMNI_COLLECTION_MEMBERSHIP_RELATION_KIND).toBe(
17
+ OmniRelationKind.Contains,
18
+ );
19
+ expect(
20
+ isCanonicalCollectionMembershipRelation({
21
+ sourceKind: OmniCollectionKind.Collection,
22
+ relationKind: OmniRelationKind.Contains,
23
+ }),
24
+ ).toBe(true);
25
+ expect(
26
+ isCanonicalCollectionMembershipRelation({
27
+ sourceKind: OmniDocumentKind.Document,
28
+ relationKind: OmniRelationKind.Contains,
29
+ }),
30
+ ).toBe(false);
31
+ });
32
+
33
+ it('recognizes the current concrete record families', () => {
34
+ expect(isOmniCollectionRecordKind(OmniCollectionKind.Collection)).toBe(
35
+ true,
36
+ );
37
+ expect(isOmniCollectionRecordKind(OmniDocumentKind.Document)).toBe(false);
38
+ expect(isOmniDocumentRecordKind(OmniDocumentKind.Document)).toBe(true);
39
+ expect(isOmniDocumentRecordKind(OmniCollectionKind.Collection)).toBe(false);
40
+ });
41
+
42
+ it('allows collection-to-document contains links and rejects the reversed organization edge', () => {
43
+ expect(
44
+ isAllowedOmniRelation({
45
+ sourceKind: OmniCollectionKind.Collection,
46
+ targetKind: OmniDocumentKind.Document,
47
+ relationKind: OmniRelationKind.Contains,
48
+ }),
49
+ ).toBe(true);
50
+ expect(
51
+ isAllowedOmniRelation({
52
+ sourceKind: OmniDocumentKind.Document,
53
+ targetKind: OmniCollectionKind.Collection,
54
+ relationKind: OmniRelationKind.Contains,
55
+ }),
56
+ ).toBe(false);
57
+ });
58
+
59
+ it('limits derived_from to document lineage', () => {
60
+ expect(
61
+ isAllowedOmniRelation({
62
+ sourceKind: OmniDocumentKind.Document,
63
+ targetKind: OmniDocumentKind.Document,
64
+ relationKind: OmniRelationKind.DerivedFrom,
65
+ }),
66
+ ).toBe(true);
67
+ expect(
68
+ isAllowedOmniRelation({
69
+ sourceKind: OmniCollectionKind.Collection,
70
+ targetKind: OmniDocumentKind.Document,
71
+ relationKind: OmniRelationKind.DerivedFrom,
72
+ }),
73
+ ).toBe(false);
74
+ });
75
+
76
+ it('keeps references and related_to flexible for cross-record graph links', () => {
77
+ expect(
78
+ isAllowedOmniRelation({
79
+ sourceKind: OmniDocumentKind.Document,
80
+ targetKind: OmniCollectionKind.Collection,
81
+ relationKind: OmniRelationKind.References,
82
+ }),
83
+ ).toBe(true);
84
+ expect(
85
+ isAllowedOmniRelation({
86
+ sourceKind: OmniCollectionKind.Collection,
87
+ targetKind: OmniDocumentKind.Document,
88
+ relationKind: OmniRelationKind.RelatedTo,
89
+ }),
90
+ ).toBe(true);
91
+ });
92
+
93
+ it('returns false for unexpected relation values at runtime', () => {
94
+ expect(
95
+ isAllowedOmniRelation({
96
+ sourceKind: OmniCollectionKind.Collection,
97
+ targetKind: OmniDocumentKind.Document,
98
+ relationKind: 'unexpected' as OmniRelationKind,
99
+ }),
100
+ ).toBe(false);
101
+ });
102
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniBaseEntity } from '../base/omni-base.entity.js';
5
+ import { OmniRelationEntity } from '../base/omni-relation.entity.js';
6
+
7
+ describe('OmniRelationEntity', () => {
8
+ it('extends OmniBaseEntity', () => {
9
+ expect(new OmniRelationEntity()).toBeInstanceOf(OmniBaseEntity);
10
+ });
11
+
12
+ it('registers the record linkage columns and relations', () => {
13
+ const metadata = getMetadataArgsStorage();
14
+ const table = metadata.tables.find(
15
+ (item) => item.target === OmniRelationEntity,
16
+ );
17
+ const entityColumns = metadata.columns
18
+ .filter((item) => item.target === OmniRelationEntity)
19
+ .map((item) => item.propertyName);
20
+ const relations = metadata.relations
21
+ .filter((item) => item.target === OmniRelationEntity)
22
+ .map((item) => item.propertyName);
23
+
24
+ expect(table?.name).toBe('omni-relation');
25
+ expect(entityColumns).toEqual(
26
+ expect.arrayContaining([
27
+ 'sourceRecordId',
28
+ 'targetRecordId',
29
+ 'kind',
30
+ 'status',
31
+ 'payload',
32
+ ]),
33
+ );
34
+ expect(relations).toEqual(
35
+ expect.arrayContaining(['sourceRecord', 'targetRecord']),
36
+ );
37
+ });
38
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it, jest } from "@jest/globals";
2
+
3
+ import { canonicalOmniRelationKinds, createOmniRelationKindContract } from '../omni-relation-kind.contract.js';
4
+ import { normalizeOmniKernelRegistrationOptions, OmniScopeContext } from '../omni-scope.js';
5
+ import { OmniScopedRepository } from '../omni-scoped.repository.js';
6
+
7
+ describe("Omni scoped substrate", () => {
8
+ it("derives a trusted scope, scopes cache keys, and rejects invalid registration", () => {
9
+ const options = normalizeOmniKernelRegistrationOptions({
10
+ dbConnection: "default",
11
+ defaultScopeId: "fallback",
12
+ resolveScope: (request) =>
13
+ (request as { authenticatedScope?: string }).authenticatedScope,
14
+ relationKinds: ["blocks"],
15
+ deletion: { record: "tombstone" },
16
+ });
17
+ const scope = new OmniScopeContext(
18
+ { req: { authenticatedScope: "scope-alpha" } },
19
+ options,
20
+ );
21
+
22
+ expect(scope.scopeId).toBe("scope-alpha");
23
+ expect(scope.cacheKey("record-1")).toBe("scope-alpha:record-1");
24
+ expect(options.deletion.document).toBe("tombstone");
25
+ expect(() => new OmniScopeContext({ req: {} }, options)).toThrow(
26
+ "unavailable",
27
+ );
28
+ expect(() =>
29
+ normalizeOmniKernelRegistrationOptions({
30
+ dbConnection: "",
31
+ }),
32
+ ).toThrow("database connection");
33
+ expect(() =>
34
+ normalizeOmniKernelRegistrationOptions({
35
+ dbConnection: "default",
36
+ deletion: { record: "erase" as never },
37
+ }),
38
+ ).toThrow("must be hard or tombstone");
39
+ expect(() =>
40
+ normalizeOmniKernelRegistrationOptions({
41
+ dbConnection: "default",
42
+ deletion: { records: "tombstone" } as never,
43
+ }),
44
+ ).toThrow("Unknown OmniKernel deletion policy resource");
45
+ });
46
+
47
+ it("keeps canonical relation kinds stable while validating registered extensions", () => {
48
+ const contract = createOmniRelationKindContract(["blocks"]);
49
+
50
+ expect(canonicalOmniRelationKinds).toContain("contains");
51
+ expect(contract.has("blocks")).toBe(true);
52
+ expect(() => contract.assert("unknown")).toThrow("not registered");
53
+ expect(() => createOmniRelationKindContract(["not portable!"])).toThrow(
54
+ "lowercase letters",
55
+ );
56
+ });
57
+
58
+ it("adds scope to lower-level repository queries and refuses caller scope input", async () => {
59
+ const repository = {
60
+ find: jest.fn(async () => []),
61
+ findOne: jest.fn(async () => null),
62
+ };
63
+ const scoped = new OmniScopedRepository(repository as never, {
64
+ scopeId: "scope-alpha",
65
+ cacheKey: (key: string) => key,
66
+ });
67
+
68
+ await scoped.findOneByGuid("record-1");
69
+ await scoped.find({ title: "Scoped record" } as never);
70
+
71
+ expect(repository.findOne).toHaveBeenCalledWith({
72
+ where: { guid: "record-1", scopeId: "scope-alpha" },
73
+ });
74
+ expect(repository.find).toHaveBeenCalledWith({
75
+ where: { title: "Scoped record", scopeId: "scope-alpha" },
76
+ });
77
+ expect(() => scoped.where({ scopeId: "scope-bravo" } as never)).toThrow(
78
+ "derived from server context",
79
+ );
80
+ });
81
+ });