@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
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # OmniKernel Module
2
+
3
+ `@nest-yalc-2/omnikernel-module` is the public package that provides the
4
+ reusable OmniKernel persistence substrate. It is built and published with the
5
+ normal framework release pipeline.
6
+
7
+ The module deliberately contains no REST controllers, GraphQL resolvers, or
8
+ application authentication. A consuming app owns those protocol and trust
9
+ boundaries by composing the exported backend factories with CrudGen.
10
+
11
+ ## Build and package boundary
12
+
13
+ The root `npm run build` first emits the shared workspace declarations and
14
+ then builds this module into `dist/src`. The package manifest exports only that
15
+ compiled directory and declares only versioned internal dependencies, so a
16
+ tarball does not depend on a consumer being adjacent to this repository.
17
+
18
+ ```bash
19
+ npm install @nest-yalc-2/omnikernel-module
20
+ ```
21
+
22
+ The source workspace builds into its local `dist/src`; the root release build
23
+ then materializes the package into `var/dist/omnikernel-module` with versioned
24
+ internal dependencies. Generated output is not committed.
25
+
26
+ ## Server-owned scope
27
+
28
+ Every Omni resource has a composite `(scopeId, guid)` identity. `scopeId` is
29
+ never an API input or output field: the request-scoped `OmniScopeContext`
30
+ derives it from a trusted adapter or an explicitly configured server default.
31
+ All generated CRUD reads, grids, mutations, relations, external references,
32
+ repositories, and dataloader cache keys use that context.
33
+
34
+ ```ts
35
+ import { OmniKernelModule } from "@nest-yalc-2/omnikernel-module";
36
+
37
+ @Module({
38
+ imports: [
39
+ OmniKernelModule.register({
40
+ dbConnection: "default",
41
+ resolveScope(request) {
42
+ return authenticateRequest(request).scopeId;
43
+ },
44
+ relationKinds: ["blocks"],
45
+ }),
46
+ ],
47
+ })
48
+ export class AppModule {}
49
+ ```
50
+
51
+ `resolveScope` must authenticate and authorize the request before returning a
52
+ scope. When a resolver is configured, an absent or invalid result fails closed
53
+ and never falls back to the default partition. The optional `defaultScopeId` is
54
+ a server configuration compatibility partition only for an app without an
55
+ adapter; it does not read a client field.
56
+
57
+ ## Resources and lifecycle
58
+
59
+ | Resource | Delete policy | Scoped database protection |
60
+ | ------------------ | ------------- | ------------------------------------------- |
61
+ | Named | hard delete | composite primary key |
62
+ | Record | tombstone | composite primary key and grid index |
63
+ | Document | tombstone | record inheritance and grid index |
64
+ | Collection | tombstone | record inheritance and grid index |
65
+ | Relation | hard delete | composite foreign keys and endpoint indexes |
66
+ | External reference | hard delete | scoped external-identity unique index |
67
+
68
+ The default policies can be overridden at registration with `deletion`. A
69
+ tombstoned resource is excluded from normal generated reads and mutations; the
70
+ row remains available for a retention or recovery workflow owned by the app.
71
+
72
+ Relations preserve the canonical `contains`, `references`, `related_to`, and
73
+ `derived_from` constants. An app may register further lowercase relation kinds
74
+ through `relationKinds`; unregistered kinds are rejected. Canonical endpoint
75
+ semantics remain enforced, while extra kinds have no hidden generic policy.
76
+
77
+ ## Omni-owned extension projections
78
+
79
+ An extension projection adds typed, generated CRUD without making a second
80
+ root identity. Its dynamically generated table has `(scopeId, guid)` and a
81
+ composite foreign key to `omni-record`; the owner remains the only authority
82
+ for scope, identity, revision, timestamps, deletion, kind, and payload-schema
83
+ metadata. The extension owns only its declared promoted columns and `payload`.
84
+
85
+ Use `createOmniExtensionProjectionEntity` and
86
+ `createOmniExtensionProjectionRegistration` from the package entrypoint. A
87
+ registration returns its TypeORM entity, one generated CrudGen REST controller,
88
+ one generated GraphQL resolver, its request-scoped service and dataloader
89
+ providers, a fixed owner-kind reservation, and a reader registration. Compose
90
+ those returned values into the app module; do not add a hand-written controller
91
+ or resolver for ordinary create/read/grid/update/delete operations.
92
+
93
+ Register every returned `reservedRecordKinds` value with
94
+ `OmniKernelModule.register`. The generic record service then rejects creating,
95
+ changing into, updating, or deleting an extension-owned record kind. The
96
+ extension service is the sole generic path that can transact the owner and its
97
+ extension row together.
98
+
99
+ The extension table uses JSON1 text on SQLite and native `jsonb` on PostgreSQL.
100
+ All JSON reads, predicates, mutation expressions, indexes, and inspection stay
101
+ inside `ProjectionDialect`; applications only declare projection metadata.
102
+ Owner revision is the optimistic-concurrency value exposed by both generated
103
+ transports.
104
+
105
+ ### Lifecycle policies and transaction readers
106
+
107
+ An optional `OmniProjectionLifecycleProvider` accepts a Nest injection token,
108
+ not a prebuilt policy instance. Give the same token to an extension and/or
109
+ relation registration. The factory resolves the request-scoped policy and runs
110
+ `beforeCreate`, `beforeUpdate`, and `beforeDelete` in the store transaction,
111
+ so REST and GraphQL have identical enforcement.
112
+
113
+ If the application and this file package resolve separate physical copies of
114
+ `@nestjs/core`, pass the application's `ModuleRef` provider token as
115
+ `moduleRefToken` to each registration. CrudGen injects that explicit token into
116
+ the generated resolver; no optional dependency or consumer cast is required.
117
+
118
+ Build one `createOmniProjectionReaderCatalogProvider` from the `reader` values
119
+ returned by the registrations. Lifecycle contexts receive the active
120
+ `EntityManager` and manager-bound `readers`, rather than repositories. An
121
+ extension reader supports declared equality filters and `take: 1..1000`; a
122
+ relation reader supports scoped source/target filters and the same bounded
123
+ `take`. Readers always enforce their registration's owner or relation metadata.
124
+ Lifecycle-bearing writes use `SERIALIZABLE`; serialization or SQLite busy
125
+ failures are reported as retryable conflicts.
126
+
127
+ ## Relation projections
128
+
129
+ `createOmniRelationProjectionRegistration` creates one generated REST,
130
+ GraphQL, and dataloader surface over the existing `omni-relation` table. It
131
+ never creates a duplicate relation table. A definition has either legacy fixed
132
+ `kind` or a non-empty unique `allowedKinds` list, fixed source/target record
133
+ kinds, and fixed status/schema metadata. For a multi-kind resource, create
134
+ requires one allowed kind; all reads, grids, updates, and deletes mechanically
135
+ constrain that set and the fixed metadata. Endpoints and kind are immutable;
136
+ payload updates require `expectedRevision` and atomically increment revision.
137
+
138
+ Definitions may map public `kind`, `source`, `target`, and `payload` aliases to
139
+ the native relation fields, keeping generated transports application-neutral.
140
+ Relations require same-scope endpoints and retain the registered endpoint-kind
141
+ checks. `OmniRelationEntity.payload` remains the existing portable
142
+ `simple-json` column on both drivers; unlike an extension projection it is not
143
+ advertised as PostgreSQL `jsonb`.
144
+
145
+ ## Versioned migration snapshots
146
+
147
+ `captureOmniMigrationSnapshot` is an authoring-only helper. Capture it while
148
+ reviewing a schema, copy the resulting literal into a versioned migration, and
149
+ pass that literal through `defineOmniMigrationSnapshot` and
150
+ `createOmniMigrationPlan`. A runtime migration never reads current TypeORM
151
+ metadata or runs synchronize. The immutable snapshot includes both table
152
+ options and dialect-compiled expression-index statements.
153
+
154
+ `plan.create(queryRunner)` creates foreign-key parents before children, then
155
+ creates expression indexes. `plan.drop(queryRunner)` drops the reverse order.
156
+ The accepted argument is the package-owned `OmniMigrationRunner` capability
157
+ contract (`createTable` and `query`), so an app's TypeORM `QueryRunner` works
158
+ without a cast even when package-manager paths duplicate TypeORM. Cross-table
159
+ foreign-key cycles and a missing referenced snapshot table fail clearly during
160
+ migration authoring.
161
+ This keeps app migrations free of repeated projection SQL and supports
162
+ reversible SQLite and PostgreSQL migrations.
163
+
164
+ ## Payload contract
165
+
166
+ Raw `payload` accepts a JSON object or `null`. Writes replace the complete
167
+ value; OmniKernel does not provide raw JSON patching or typed payload filters.
168
+ `payloadSchemaId` and positive `payloadSchemaVersion` are supplied together to
169
+ identify the app-owned schema revision.
170
+
171
+ The raw Omni payload is `simple-json`, so it is portable but opaque to the
172
+ generic API, including PostgreSQL. Use the separate CrudGen scoped projection
173
+ contract when a resource needs native PostgreSQL `jsonb`, typed filtering,
174
+ sorting, or revision patch semantics; see
175
+ [`docs/crud-gen-projections.md`](../../../docs/crud-gen-projections.md).
176
+
177
+ ## Indexes and bounded diagnostics
178
+
179
+ The schema declares driver-portable composite indexes for record grid reads,
180
+ relation source/target traversal, and external-reference lookup, plus scoped
181
+ uniqueness for relations and external identities. `collectOmniKernelQueryPlanEvidence`
182
+ uses diagnostic-only `EXPLAIN` statements for the record grid and relation
183
+ source shapes. It does not replace TypeORM CRUD or certify a production scale
184
+ SLA; callers must run their own production-volume measurements.
185
+
186
+ ## Exports
187
+
188
+ The package exports entities, DTO metadata, scoped services and repositories,
189
+ dataloaders, backend provider factories, relation-kind helpers, query helpers,
190
+ and diagnostic helpers. Runtime imports resolve to `dist`, not TypeScript
191
+ source files.
192
+
193
+ Use `examples/omnikernel/app` for generated REST and GraphQL composition.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@nest-yalc-2/omnikernel-module",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "description": "Foundational OmniKernel persistence module for nestjs-yalc",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./*.js": "./src/*.js",
11
+ "./*": "./src/*"
12
+ },
13
+ "author": "",
14
+ "license": "MIT",
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "dependencies": {
20
+ "@nest-yalc-2/crud-gen": "^1.5.0",
21
+ "@nest-yalc-2/data-loader": "^1.0.0",
22
+ "@nest-yalc-2/event-manager": "^1.3.4",
23
+ "@nest-yalc-2/graphql": "^1.0.0",
24
+ "@nest-yalc-2/types": "^1.0.0",
25
+ "@nest-yalc-2/utils": "^1.0.0",
26
+ "tslib": "^2.8.1"
27
+ },
28
+ "peerDependencies": {
29
+ "@nestjs/common": "^11.1.19",
30
+ "@nestjs/core": "^11.1.19",
31
+ "@nestjs/event-emitter": "^3.0.1",
32
+ "@nestjs/graphql": "^13.2.5",
33
+ "@nestjs/typeorm": "^11.0.1",
34
+ "class-transformer": "^0.5.1",
35
+ "class-validator": "^0.15.1",
36
+ "graphql": "^16.13.2",
37
+ "graphql-type-json": "^0.3.2",
38
+ "reflect-metadata": "^0.2.2",
39
+ "typeorm": "^0.3.28"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/Nek97/nestjs-yalc.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/Nek97/nestjs-yalc/issues"
47
+ },
48
+ "homepage": "https://github.com/Nek97/nestjs-yalc#readme",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniRecordEntity } from '../base/omni-record.entity.js';
5
+ import { OmniCollectionEntity } from '../omni-collection.entity.js';
6
+ import { OmniCollectionKind } from '../omni-collection-kind.enum.js';
7
+
8
+ describe('OmniCollectionEntity', () => {
9
+ it('extends OmniRecordEntity', () => {
10
+ expect(new OmniCollectionEntity()).toBeInstanceOf(OmniRecordEntity);
11
+ });
12
+
13
+ it('stores collections as child records in the omni-record table', () => {
14
+ const metadata = getMetadataArgsStorage();
15
+ const recordTable = metadata.tables.find(
16
+ (item) => item.target === OmniRecordEntity,
17
+ );
18
+ const collectionTable = metadata.tables.find(
19
+ (item) => item.target === OmniCollectionEntity,
20
+ );
21
+ const inheritance = metadata.inheritances.find(
22
+ (item) => item.target === OmniRecordEntity,
23
+ );
24
+ const entityColumns = metadata.columns
25
+ .filter((item) => item.target === OmniCollectionEntity)
26
+ .map((item) => item.propertyName);
27
+
28
+ expect(recordTable?.name).toBe('omni-record');
29
+ expect(collectionTable?.type).toBe('entity-child');
30
+ expect(inheritance?.column).toMatchObject({ name: 'recordType' });
31
+ expect(entityColumns).toEqual(
32
+ expect.arrayContaining(['collectionKind', 'summary']),
33
+ );
34
+ });
35
+
36
+ it('defaults the base record kind to collection in the class instance', () => {
37
+ const entity = new OmniCollectionEntity();
38
+
39
+ expect(entity.kind).toBe(OmniCollectionKind.Collection);
40
+ });
41
+
42
+ it('allows explicit collection subtypes while keeping the base record kind stable', () => {
43
+ const entity = new OmniCollectionEntity();
44
+ entity.collectionKind = OmniCollectionKind.Folder;
45
+
46
+ expect(entity.kind).toBe(OmniCollectionKind.Collection);
47
+ expect(entity.collectionKind).toBe(OmniCollectionKind.Folder);
48
+ });
49
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, expect, it, jest } from '@jest/globals';
2
+
3
+ import { OmniCollectionService } from '../omni-collection.service.js';
4
+ import { OmniCollectionEntity } from '../omni-collection.entity.js';
5
+ import { OmniCollectionKind } from '../omni-collection-kind.enum.js';
6
+
7
+ const createRepositoryPair = () => {
8
+ const readRepository = {
9
+ target: OmniCollectionEntity,
10
+ findOneOrFail: jest.fn(),
11
+ getId: jest.fn((entity: OmniCollectionEntity) => ({ guid: entity.guid })),
12
+ };
13
+ const writeRepository = {
14
+ target: OmniCollectionEntity,
15
+ create: jest.fn((input: unknown) => input),
16
+ insert: jest.fn(async () => ({
17
+ identifiers: [{ guid: 'aaaaaaaa-1111-1111-1111-111111111111' }],
18
+ })),
19
+ update: jest.fn(async () => ({ affected: 1 })),
20
+ };
21
+
22
+ return { readRepository, writeRepository };
23
+ };
24
+
25
+ describe('OmniCollectionService', () => {
26
+ it('forces the base record kind during createEntity', async () => {
27
+ const { readRepository, writeRepository } = createRepositoryPair();
28
+ const service = new OmniCollectionService(
29
+ readRepository as never,
30
+ writeRepository as never,
31
+ );
32
+
33
+ await service.createEntity(
34
+ {
35
+ guid: 'aaaaaaaa-1111-1111-1111-111111111111',
36
+ title: 'Collection',
37
+ kind: 'unexpected-kind',
38
+ collectionKind: OmniCollectionKind.Folder,
39
+ },
40
+ undefined,
41
+ false,
42
+ );
43
+
44
+ expect(writeRepository.insert).toHaveBeenCalledWith(
45
+ expect.objectContaining({
46
+ kind: OmniCollectionKind.Collection,
47
+ collectionKind: OmniCollectionKind.Folder,
48
+ }),
49
+ );
50
+ });
51
+
52
+ it('forces the base record kind during updateEntity', async () => {
53
+ const { readRepository, writeRepository } = createRepositoryPair();
54
+ const service = new OmniCollectionService(
55
+ readRepository as never,
56
+ writeRepository as never,
57
+ );
58
+ (service as any).validateConditions = jest.fn(async () => ({
59
+ guid: 'aaaaaaaa-1111-1111-1111-111111111111',
60
+ }));
61
+
62
+ await service.updateEntity(
63
+ { guid: 'aaaaaaaa-1111-1111-1111-111111111111' },
64
+ {
65
+ kind: 'unexpected-kind',
66
+ collectionKind: OmniCollectionKind.Folder,
67
+ },
68
+ undefined,
69
+ false,
70
+ );
71
+
72
+ expect(writeRepository.update).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ guid: 'aaaaaaaa-1111-1111-1111-111111111111',
75
+ kind: OmniCollectionKind.Collection,
76
+ }),
77
+ expect.objectContaining({
78
+ kind: OmniCollectionKind.Collection,
79
+ collectionKind: OmniCollectionKind.Folder,
80
+ }),
81
+ );
82
+ });
83
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { getMetadataArgsStorage } from 'typeorm';
3
+
4
+ import { OmniRecordEntity } from '../base/omni-record.entity.js';
5
+ import { OmniDocumentEntity } from '../omni-document.entity.js';
6
+ import { OmniDocumentKind } from '../omni-document-kind.enum.js';
7
+
8
+ describe('OmniDocumentEntity', () => {
9
+ it('extends OmniRecordEntity', () => {
10
+ expect(new OmniDocumentEntity()).toBeInstanceOf(OmniRecordEntity);
11
+ });
12
+
13
+ it('stores documents as child records in the omni-record table', () => {
14
+ const metadata = getMetadataArgsStorage();
15
+ const recordTable = metadata.tables.find(
16
+ (item) => item.target === OmniRecordEntity,
17
+ );
18
+ const documentTable = metadata.tables.find(
19
+ (item) => item.target === OmniDocumentEntity,
20
+ );
21
+ const inheritance = metadata.inheritances.find(
22
+ (item) => item.target === OmniRecordEntity,
23
+ );
24
+ const entityColumns = metadata.columns
25
+ .filter((item) => item.target === OmniDocumentEntity)
26
+ .map((item) => item.propertyName);
27
+
28
+ expect(recordTable?.name).toBe('omni-record');
29
+ expect(documentTable?.type).toBe('entity-child');
30
+ expect(inheritance?.column).toMatchObject({ name: 'recordType' });
31
+ expect(entityColumns).toEqual(
32
+ expect.arrayContaining([
33
+ 'documentKind',
34
+ 'content',
35
+ 'contentMimeType',
36
+ 'sourceUrl',
37
+ 'publishedAt',
38
+ ]),
39
+ );
40
+ });
41
+
42
+ it('defaults the base record kind to document in the class instance', () => {
43
+ const entity = new OmniDocumentEntity();
44
+
45
+ expect(entity.kind).toBe(OmniDocumentKind.Document);
46
+ });
47
+
48
+ it('allows explicit document subtypes while keeping the base record kind stable', () => {
49
+ const entity = new OmniDocumentEntity();
50
+ entity.documentKind = OmniDocumentKind.Note;
51
+
52
+ expect(entity.kind).toBe(OmniDocumentKind.Document);
53
+ expect(entity.documentKind).toBe(OmniDocumentKind.Note);
54
+ });
55
+ });
@@ -0,0 +1,83 @@
1
+ import { describe, expect, it, jest } from '@jest/globals';
2
+
3
+ import { OmniDocumentService } from '../omni-document.service.js';
4
+ import { OmniDocumentEntity } from '../omni-document.entity.js';
5
+ import { OmniDocumentKind } from '../omni-document-kind.enum.js';
6
+
7
+ const createRepositoryPair = () => {
8
+ const readRepository = {
9
+ target: OmniDocumentEntity,
10
+ findOneOrFail: jest.fn(),
11
+ getId: jest.fn((entity: OmniDocumentEntity) => ({ guid: entity.guid })),
12
+ };
13
+ const writeRepository = {
14
+ target: OmniDocumentEntity,
15
+ create: jest.fn((input: unknown) => input),
16
+ insert: jest.fn(async () => ({
17
+ identifiers: [{ guid: '11111111-1111-1111-1111-111111111111' }],
18
+ })),
19
+ update: jest.fn(async () => ({ affected: 1 })),
20
+ };
21
+
22
+ return { readRepository, writeRepository };
23
+ };
24
+
25
+ describe('OmniDocumentService', () => {
26
+ it('forces the base record kind during createEntity', async () => {
27
+ const { readRepository, writeRepository } = createRepositoryPair();
28
+ const service = new OmniDocumentService(
29
+ readRepository as never,
30
+ writeRepository as never,
31
+ );
32
+
33
+ await service.createEntity(
34
+ {
35
+ guid: '11111111-1111-1111-1111-111111111111',
36
+ title: 'Document',
37
+ kind: 'unexpected-kind',
38
+ documentKind: OmniDocumentKind.Note,
39
+ },
40
+ undefined,
41
+ false,
42
+ );
43
+
44
+ expect(writeRepository.insert).toHaveBeenCalledWith(
45
+ expect.objectContaining({
46
+ kind: OmniDocumentKind.Document,
47
+ documentKind: OmniDocumentKind.Note,
48
+ }),
49
+ );
50
+ });
51
+
52
+ it('forces the base record kind during updateEntity', async () => {
53
+ const { readRepository, writeRepository } = createRepositoryPair();
54
+ const service = new OmniDocumentService(
55
+ readRepository as never,
56
+ writeRepository as never,
57
+ );
58
+ (service as any).validateConditions = jest.fn(async () => ({
59
+ guid: '11111111-1111-1111-1111-111111111111',
60
+ }));
61
+
62
+ await service.updateEntity(
63
+ { guid: '11111111-1111-1111-1111-111111111111' },
64
+ {
65
+ kind: 'unexpected-kind',
66
+ documentKind: OmniDocumentKind.Article,
67
+ },
68
+ undefined,
69
+ false,
70
+ );
71
+
72
+ expect(writeRepository.update).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ guid: '11111111-1111-1111-1111-111111111111',
75
+ kind: OmniDocumentKind.Document,
76
+ }),
77
+ expect.objectContaining({
78
+ kind: OmniDocumentKind.Document,
79
+ documentKind: OmniDocumentKind.Article,
80
+ }),
81
+ );
82
+ });
83
+ });