@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.
- package/LICENSE +661 -0
- package/README.md +193 -0
- package/package.json +52 -0
- package/src/__tests__/omni-collection.entity.spec.ts +49 -0
- package/src/__tests__/omni-collection.service.spec.ts +83 -0
- package/src/__tests__/omni-document.entity.spec.ts +55 -0
- package/src/__tests__/omni-document.service.spec.ts +83 -0
- package/src/__tests__/omni-extension-projection-constraints.spec.ts +518 -0
- package/src/__tests__/omni-extension-projection.registration.spec.ts +946 -0
- package/src/__tests__/omni-extension-projection.service.spec.ts +806 -0
- package/src/__tests__/omni-external-ref.entity.spec.ts +36 -0
- package/src/__tests__/omni-external-ref.service.spec.ts +252 -0
- package/src/__tests__/omni-named.entity.spec.ts +33 -0
- package/src/__tests__/omni-record.entity.spec.ts +32 -0
- package/src/__tests__/omni-record.service.spec.ts +107 -0
- package/src/__tests__/omni-relation-semantics.spec.ts +102 -0
- package/src/__tests__/omni-relation.entity.spec.ts +38 -0
- package/src/__tests__/omni-scope-substrate.spec.ts +81 -0
- package/src/__tests__/omni-scoped.backend.spec.ts +115 -0
- package/src/__tests__/omni-scoped.service.spec.ts +354 -0
- package/src/__tests__/omnikernel.diagnostics.spec.ts +94 -0
- package/src/__tests__/omnikernel.module.spec.ts +40 -0
- package/src/__tests__/omnikernel.persistence.spec.ts +154 -0
- package/src/__tests__/omnikernel.public-api.spec.ts +238 -0
- package/src/__tests__/omnikernel.query.service.spec.ts +155 -0
- package/src/__tests__/tsconfig.json +6 -0
- package/src/base/omni-base.entity.d.ts +9 -0
- package/src/base/omni-base.entity.js +53 -0
- package/src/base/omni-base.entity.js.map +1 -0
- package/src/base/omni-base.entity.ts +41 -0
- package/src/base/omni-external-ref.entity.d.ts +13 -0
- package/src/base/omni-external-ref.entity.js +72 -0
- package/src/base/omni-external-ref.entity.js.map +1 -0
- package/src/base/omni-external-ref.entity.ts +53 -0
- package/src/base/omni-named.entity.d.ts +6 -0
- package/src/base/omni-named.entity.js +35 -0
- package/src/base/omni-named.entity.js.map +1 -0
- package/src/base/omni-named.entity.ts +16 -0
- package/src/base/omni-record.entity.d.ts +13 -0
- package/src/base/omni-record.entity.js +71 -0
- package/src/base/omni-record.entity.js.map +1 -0
- package/src/base/omni-record.entity.ts +51 -0
- package/src/base/omni-relation.entity.d.ts +15 -0
- package/src/base/omni-relation.entity.js +91 -0
- package/src/base/omni-relation.entity.js.map +1 -0
- package/src/base/omni-relation.entity.ts +72 -0
- package/src/index.d.ts +49 -0
- package/src/index.js +66 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +49 -0
- package/src/omni-collection-kind.enum.d.ts +4 -0
- package/src/omni-collection-kind.enum.js +13 -0
- package/src/omni-collection-kind.enum.js.map +1 -0
- package/src/omni-collection-kind.enum.ts +10 -0
- package/src/omni-collection.backend.d.ts +2 -0
- package/src/omni-collection.backend.js +15 -0
- package/src/omni-collection.backend.js.map +1 -0
- package/src/omni-collection.backend.ts +16 -0
- package/src/omni-collection.dto.d.ts +32 -0
- package/src/omni-collection.dto.js +184 -0
- package/src/omni-collection.dto.js.map +1 -0
- package/src/omni-collection.dto.ts +170 -0
- package/src/omni-collection.entity.d.ts +7 -0
- package/src/omni-collection.entity.js +41 -0
- package/src/omni-collection.entity.js.map +1 -0
- package/src/omni-collection.entity.ts +21 -0
- package/src/omni-collection.service.d.ts +14 -0
- package/src/omni-collection.service.js +24 -0
- package/src/omni-collection.service.js.map +1 -0
- package/src/omni-collection.service.ts +75 -0
- package/src/omni-document-kind.enum.d.ts +6 -0
- package/src/omni-document-kind.enum.js +15 -0
- package/src/omni-document-kind.enum.js.map +1 -0
- package/src/omni-document-kind.enum.ts +12 -0
- package/src/omni-document.backend.d.ts +2 -0
- package/src/omni-document.backend.js +15 -0
- package/src/omni-document.backend.js.map +1 -0
- package/src/omni-document.backend.ts +16 -0
- package/src/omni-document.dto.d.ts +35 -0
- package/src/omni-document.dto.js +213 -0
- package/src/omni-document.dto.js.map +1 -0
- package/src/omni-document.dto.ts +198 -0
- package/src/omni-document.entity.d.ts +10 -0
- package/src/omni-document.entity.js +53 -0
- package/src/omni-document.entity.js.map +1 -0
- package/src/omni-document.entity.ts +30 -0
- package/src/omni-document.service.d.ts +14 -0
- package/src/omni-document.service.js +24 -0
- package/src/omni-document.service.js.map +1 -0
- package/src/omni-document.service.ts +75 -0
- package/src/omni-dto.helpers.d.ts +1 -0
- package/src/omni-dto.helpers.js +10 -0
- package/src/omni-dto.helpers.js.map +1 -0
- package/src/omni-dto.helpers.ts +21 -0
- package/src/omni-extension-projection.definition.d.ts +17 -0
- package/src/omni-extension-projection.definition.js +160 -0
- package/src/omni-extension-projection.definition.js.map +1 -0
- package/src/omni-extension-projection.definition.ts +294 -0
- package/src/omni-extension-projection.resource.d.ts +42 -0
- package/src/omni-extension-projection.resource.js +127 -0
- package/src/omni-extension-projection.resource.js.map +1 -0
- package/src/omni-extension-projection.resource.ts +274 -0
- package/src/omni-extension-projection.service.d.ts +33 -0
- package/src/omni-extension-projection.service.js +332 -0
- package/src/omni-extension-projection.service.js.map +1 -0
- package/src/omni-extension-projection.service.ts +452 -0
- package/src/omni-external-ref-binding.validator.d.ts +14 -0
- package/src/omni-external-ref-binding.validator.js +34 -0
- package/src/omni-external-ref-binding.validator.js.map +1 -0
- package/src/omni-external-ref-binding.validator.ts +53 -0
- package/src/omni-external-ref-internal-type.enum.d.ts +5 -0
- package/src/omni-external-ref-internal-type.enum.js +14 -0
- package/src/omni-external-ref-internal-type.enum.js.map +1 -0
- package/src/omni-external-ref-internal-type.enum.ts +11 -0
- package/src/omni-external-ref.backend.d.ts +2 -0
- package/src/omni-external-ref.backend.js +19 -0
- package/src/omni-external-ref.backend.js.map +1 -0
- package/src/omni-external-ref.backend.ts +25 -0
- package/src/omni-external-ref.dto.d.ts +26 -0
- package/src/omni-external-ref.dto.js +140 -0
- package/src/omni-external-ref.dto.js.map +1 -0
- package/src/omni-external-ref.dto.ts +128 -0
- package/src/omni-external-ref.service.d.ts +33 -0
- package/src/omni-external-ref.service.js +133 -0
- package/src/omni-external-ref.service.js.map +1 -0
- package/src/omni-external-ref.service.ts +255 -0
- package/src/omni-migration.d.ts +28 -0
- package/src/omni-migration.js +239 -0
- package/src/omni-migration.js.map +1 -0
- package/src/omni-migration.ts +365 -0
- package/src/omni-named.backend.d.ts +2 -0
- package/src/omni-named.backend.js +13 -0
- package/src/omni-named.backend.js.map +1 -0
- package/src/omni-named.backend.ts +11 -0
- package/src/omni-named.dto.d.ts +19 -0
- package/src/omni-named.dto.js +94 -0
- package/src/omni-named.dto.js.map +1 -0
- package/src/omni-named.dto.ts +86 -0
- package/src/omni-projection.catalog.d.ts +49 -0
- package/src/omni-projection.catalog.js +177 -0
- package/src/omni-projection.catalog.js.map +1 -0
- package/src/omni-projection.catalog.ts +345 -0
- package/src/omni-projection.lifecycle.d.ts +20 -0
- package/src/omni-projection.lifecycle.js +3 -0
- package/src/omni-projection.lifecycle.js.map +1 -0
- package/src/omni-projection.lifecycle.ts +53 -0
- package/src/omni-record-status.enum.d.ts +5 -0
- package/src/omni-record-status.enum.js +14 -0
- package/src/omni-record-status.enum.js.map +1 -0
- package/src/omni-record-status.enum.ts +11 -0
- package/src/omni-record.backend.d.ts +2 -0
- package/src/omni-record.backend.js +13 -0
- package/src/omni-record.backend.js.map +1 -0
- package/src/omni-record.backend.ts +19 -0
- package/src/omni-record.dto.d.ts +29 -0
- package/src/omni-record.dto.js +169 -0
- package/src/omni-record.dto.js.map +1 -0
- package/src/omni-record.dto.ts +157 -0
- package/src/omni-record.service.d.ts +18 -0
- package/src/omni-record.service.js +45 -0
- package/src/omni-record.service.js.map +1 -0
- package/src/omni-record.service.ts +113 -0
- package/src/omni-relation-kind.contract.d.ts +9 -0
- package/src/omni-relation-kind.contract.js +33 -0
- package/src/omni-relation-kind.contract.js.map +1 -0
- package/src/omni-relation-kind.contract.ts +49 -0
- package/src/omni-relation-kind.enum.d.ts +6 -0
- package/src/omni-relation-kind.enum.js +15 -0
- package/src/omni-relation-kind.enum.js.map +1 -0
- package/src/omni-relation-kind.enum.ts +12 -0
- package/src/omni-relation-projection.definition.d.ts +25 -0
- package/src/omni-relation-projection.definition.js +83 -0
- package/src/omni-relation-projection.definition.js.map +1 -0
- package/src/omni-relation-projection.definition.ts +166 -0
- package/src/omni-relation-projection.resource.d.ts +51 -0
- package/src/omni-relation-projection.resource.js +203 -0
- package/src/omni-relation-projection.resource.js.map +1 -0
- package/src/omni-relation-projection.resource.ts +360 -0
- package/src/omni-relation-projection.service.d.ts +47 -0
- package/src/omni-relation-projection.service.js +385 -0
- package/src/omni-relation-projection.service.js.map +1 -0
- package/src/omni-relation-projection.service.ts +610 -0
- package/src/omni-relation-semantics.d.ts +11 -0
- package/src/omni-relation-semantics.js +34 -0
- package/src/omni-relation-semantics.js.map +1 -0
- package/src/omni-relation-semantics.ts +52 -0
- package/src/omni-relation-status.enum.d.ts +5 -0
- package/src/omni-relation-status.enum.js +14 -0
- package/src/omni-relation-status.enum.js.map +1 -0
- package/src/omni-relation-status.enum.ts +11 -0
- package/src/omni-relation.backend.d.ts +2 -0
- package/src/omni-relation.backend.js +17 -0
- package/src/omni-relation.backend.js.map +1 -0
- package/src/omni-relation.backend.ts +21 -0
- package/src/omni-relation.dto.d.ts +28 -0
- package/src/omni-relation.dto.js +153 -0
- package/src/omni-relation.dto.js.map +1 -0
- package/src/omni-relation.dto.ts +142 -0
- package/src/omni-relation.service.d.ts +19 -0
- package/src/omni-relation.service.js +76 -0
- package/src/omni-relation.service.js.map +1 -0
- package/src/omni-relation.service.ts +159 -0
- package/src/omni-scope.d.ts +33 -0
- package/src/omni-scope.js +91 -0
- package/src/omni-scope.js.map +1 -0
- package/src/omni-scope.ts +161 -0
- package/src/omni-scoped.backend.d.ts +21 -0
- package/src/omni-scoped.backend.js +47 -0
- package/src/omni-scoped.backend.js.map +1 -0
- package/src/omni-scoped.backend.ts +110 -0
- package/src/omni-scoped.repository.d.ts +10 -0
- package/src/omni-scoped.repository.js +26 -0
- package/src/omni-scoped.repository.js.map +1 -0
- package/src/omni-scoped.repository.ts +34 -0
- package/src/omni-scoped.service.d.ts +36 -0
- package/src/omni-scoped.service.js +171 -0
- package/src/omni-scoped.service.js.map +1 -0
- package/src/omni-scoped.service.ts +338 -0
- package/src/omnikernel.diagnostics.d.ts +16 -0
- package/src/omnikernel.diagnostics.js +50 -0
- package/src/omnikernel.diagnostics.js.map +1 -0
- package/src/omnikernel.diagnostics.ts +83 -0
- package/src/omnikernel.module.d.ts +5 -0
- package/src/omnikernel.module.js +87 -0
- package/src/omnikernel.module.js.map +1 -0
- package/src/omnikernel.module.ts +97 -0
- package/src/omnikernel.query.service.d.ts +17 -0
- package/src/omnikernel.query.service.js +85 -0
- package/src/omnikernel.query.service.js.map +1 -0
- 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
|
+
});
|