feat: implement demo candidate 1.0.0-rc.1
This commit is contained in:
14
src/config/app.default.ts
Normal file
14
src/config/app.default.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { MidwayConfig } from '@midwayjs/core';
|
||||
import { defineSaasServiceHostConfig } from '@cool-midway/rpc/saas-host';
|
||||
import { GeneratedSaasModule } from '../saas/generated';
|
||||
|
||||
const host = defineSaasServiceHostConfig({
|
||||
generated: GeneratedSaasModule,
|
||||
});
|
||||
|
||||
export default {
|
||||
...host,
|
||||
keys: 'saas-module-service-local-v1',
|
||||
koa: { port: Number(process.env.PORT || 7101) },
|
||||
asyncContextManager: { enable: true },
|
||||
} as MidwayConfig;
|
||||
12
src/configuration.ts
Normal file
12
src/configuration.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Configuration } from '@midwayjs/core';
|
||||
import * as koa from '@midwayjs/koa';
|
||||
import * as validate from '@midwayjs/validate';
|
||||
import * as rpc from '@cool-midway/rpc';
|
||||
import * as saasHost from '@cool-midway/rpc/saas-host';
|
||||
import * as DefaultConfig from './config/app.default';
|
||||
|
||||
@Configuration({
|
||||
imports: [koa, validate, rpc, saasHost],
|
||||
importConfigs: [{ default: DefaultConfig }],
|
||||
})
|
||||
export class SaasModuleServiceConfiguration {}
|
||||
90
src/controller/health.ts
Normal file
90
src/controller/health.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Config, Controller, Get, Inject } from '@midwayjs/core';
|
||||
import type { Context } from '@midwayjs/koa';
|
||||
import type {
|
||||
RpcHostReadiness,
|
||||
RpcHostReadinessSnapshot,
|
||||
} from '@cool-midway/rpc/saas-host';
|
||||
|
||||
@Controller('/health')
|
||||
export class SaasModuleHealthController {
|
||||
@Inject()
|
||||
ctx: Context;
|
||||
|
||||
@Config('cool.rpc.moduleCode')
|
||||
moduleCode: string;
|
||||
|
||||
@Config('cool.rpc.serviceVersion')
|
||||
serviceVersion: string;
|
||||
|
||||
@Config('cool.rpc.minSchemaVersion')
|
||||
minSchemaVersion: number;
|
||||
|
||||
@Config('cool.rpc.maxSchemaVersion')
|
||||
maxSchemaVersion: number;
|
||||
|
||||
@Get('/')
|
||||
async health() {
|
||||
const readiness = await this.resolveReadiness();
|
||||
if (!readiness.ready) this.ctx.status = 503;
|
||||
return {
|
||||
...readiness,
|
||||
moduleCode: this.moduleCode,
|
||||
serviceVersion: this.serviceVersion,
|
||||
schemaRange: [this.minSchemaVersion, this.maxSchemaVersion],
|
||||
};
|
||||
}
|
||||
|
||||
private async resolveReadiness(): Promise<RpcHostReadinessSnapshot> {
|
||||
try {
|
||||
const container = this.ctx?.requestContext;
|
||||
if (!container) {
|
||||
return this.unavailable('RPC_HOST_READINESS_PROVIDER_MISSING');
|
||||
}
|
||||
const readiness = await container.getAsync<RpcHostReadiness>(
|
||||
'rpcHostReadiness'
|
||||
);
|
||||
if (!readiness) {
|
||||
return this.unavailable('RPC_HOST_READINESS_PROVIDER_MISSING');
|
||||
}
|
||||
const snapshot = readiness?.snapshot?.();
|
||||
return this.isReadinessSnapshot(snapshot)
|
||||
? snapshot
|
||||
: this.unavailable('RPC_HOST_READINESS_SNAPSHOT_INVALID');
|
||||
} catch {
|
||||
return this.unavailable('RPC_HOST_READINESS_UNAVAILABLE');
|
||||
}
|
||||
}
|
||||
|
||||
private isReadinessSnapshot(
|
||||
snapshot: RpcHostReadinessSnapshot | undefined
|
||||
): snapshot is RpcHostReadinessSnapshot {
|
||||
const checkStates = ['pending', 'ok', 'error'];
|
||||
return Boolean(
|
||||
snapshot &&
|
||||
typeof snapshot.ready === 'boolean' &&
|
||||
['starting', 'ready', 'degraded', 'draining', 'stopped'].includes(
|
||||
snapshot.status
|
||||
) &&
|
||||
snapshot.ready === (snapshot.status === 'ready') &&
|
||||
Number.isFinite(snapshot.checkedAt) &&
|
||||
snapshot.checks &&
|
||||
checkStates.includes(snapshot.checks.bootstrap) &&
|
||||
checkStates.includes(snapshot.checks.lease) &&
|
||||
checkStates.includes(snapshot.checks.registry)
|
||||
);
|
||||
}
|
||||
|
||||
private unavailable(reason: string): RpcHostReadinessSnapshot {
|
||||
return {
|
||||
ready: false,
|
||||
status: 'degraded',
|
||||
reason,
|
||||
checkedAt: Date.now(),
|
||||
checks: {
|
||||
bootstrap: 'error',
|
||||
lease: 'error',
|
||||
registry: 'error',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
1
src/saas/generated/.gitkeep
Normal file
1
src/saas/generated/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1104
src/saas/generated/demo/contracts.ts
Normal file
1104
src/saas/generated/demo/contracts.ts
Normal file
File diff suppressed because it is too large
Load Diff
5
src/saas/generated/demo/index.ts
Normal file
5
src/saas/generated/demo/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './models';
|
||||
export * from './contracts';
|
||||
export * from './saas-actions';
|
||||
export * from './saas-events';
|
||||
export * from './module';
|
||||
78
src/saas/generated/demo/models.ts
Normal file
78
src/saas/generated/demo/models.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||
import {
|
||||
SaasReadModel,
|
||||
SaasTableModel,
|
||||
defineSaasReadModel,
|
||||
defineSaasTableModel
|
||||
} from '@cool-midway/module-runtime';
|
||||
|
||||
export interface GoodsRow extends Record<string, unknown> {
|
||||
id: string;
|
||||
title: string;
|
||||
price: string;
|
||||
stock: number;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export class Goods extends SaasTableModel<GoodsRow> {
|
||||
static readonly definition = defineSaasTableModel({
|
||||
"kind": "table",
|
||||
"moduleCode": "demo",
|
||||
"revision": 1,
|
||||
"schemaVersion": 1,
|
||||
"schemaInputChecksum": "8a55cecb643f802ef0f39ade2f31fe8313ad437d72d12b64525c0b1a6892813d",
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
{
|
||||
"code": "id",
|
||||
"type": "bigint",
|
||||
"nullable": false,
|
||||
"primary": true,
|
||||
"readable": true,
|
||||
"writable": false,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"code": "title",
|
||||
"type": "varchar",
|
||||
"nullable": false,
|
||||
"primary": false,
|
||||
"readable": true,
|
||||
"writable": true,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"code": "price",
|
||||
"type": "decimal",
|
||||
"nullable": false,
|
||||
"primary": false,
|
||||
"readable": true,
|
||||
"writable": true,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"code": "stock",
|
||||
"type": "int",
|
||||
"nullable": false,
|
||||
"primary": false,
|
||||
"readable": true,
|
||||
"writable": true,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"code": "status",
|
||||
"type": "int",
|
||||
"nullable": false,
|
||||
"primary": false,
|
||||
"readable": true,
|
||||
"writable": true,
|
||||
"filterable": true,
|
||||
"sortable": true
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
12
src/saas/generated/demo/module.ts
Normal file
12
src/saas/generated/demo/module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||
import { defineGeneratedSaasModule } from '@cool-midway/module-runtime';
|
||||
|
||||
export const GeneratedSaasModule = defineGeneratedSaasModule({
|
||||
"formatVersion": 2,
|
||||
"moduleCode": "demo",
|
||||
"revision": 1,
|
||||
"schemaVersion": 1,
|
||||
"schemaInputChecksum": "8a55cecb643f802ef0f39ade2f31fe8313ad437d72d12b64525c0b1a6892813d",
|
||||
"runtimeContractChecksum": "c1327810dd08dfd0cc1afeee99d7e9707fe14595272a3aa92ee13fc1d54aa0e5",
|
||||
"implementationManifestChecksum": "d18964a3d40e3c9a65d83996ca3141a56043986c395f5d4cac57c60aa72f4236"
|
||||
});
|
||||
2
src/saas/generated/demo/saas-actions.ts
Normal file
2
src/saas/generated/demo/saas-actions.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||
export const SaasActions = {} as const;
|
||||
2
src/saas/generated/demo/saas-events.ts
Normal file
2
src/saas/generated/demo/saas-events.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Generated by cool saas-model generate. DO NOT EDIT.
|
||||
export const SaasEvents = {} as const;
|
||||
957
src/saas/generated/demo/saas-implementation.expected.json
Normal file
957
src/saas/generated/demo/saas-implementation.expected.json
Normal file
@@ -0,0 +1,957 @@
|
||||
{
|
||||
"manifestVersion": 1,
|
||||
"moduleCode": "demo",
|
||||
"revision": 1,
|
||||
"schemaChecksum": null,
|
||||
"schemaInputChecksum": "8a55cecb643f802ef0f39ade2f31fe8313ad437d72d12b64525c0b1a6892813d",
|
||||
"actionManifestChecksum": null,
|
||||
"runtimeContractChecksum": "c1327810dd08dfd0cc1afeee99d7e9707fe14595272a3aa92ee13fc1d54aa0e5",
|
||||
"implementationManifestChecksum": "d18964a3d40e3c9a65d83996ca3141a56043986c395f5d4cac57c60aa72f4236",
|
||||
"actions": [
|
||||
"GoodsAdd",
|
||||
"GoodsBatchInfo",
|
||||
"GoodsDelete",
|
||||
"GoodsInfo",
|
||||
"GoodsList",
|
||||
"GoodsPage",
|
||||
"GoodsReleaseStock",
|
||||
"GoodsReserveStock",
|
||||
"GoodsUpdate"
|
||||
],
|
||||
"eventHandlers": [
|
||||
"RuntimeSaasSagaCompensationCompleted",
|
||||
"RuntimeSaasSagaCompensationFailed",
|
||||
"RuntimeSaasSagaStepCompleted",
|
||||
"RuntimeSaasSagaStepFailed"
|
||||
],
|
||||
"runtimeManagedEventHandlers": [
|
||||
"RuntimeSaasSagaCompensationCompleted",
|
||||
"RuntimeSaasSagaCompensationFailed",
|
||||
"RuntimeSaasSagaStepCompleted",
|
||||
"RuntimeSaasSagaStepFailed"
|
||||
],
|
||||
"runtimeManagedActions": [
|
||||
"GoodsAdd",
|
||||
"GoodsDelete",
|
||||
"GoodsInfo",
|
||||
"GoodsList",
|
||||
"GoodsPage",
|
||||
"GoodsUpdate"
|
||||
],
|
||||
"crudDescriptors": [
|
||||
{
|
||||
"exportName": "Goods",
|
||||
"tableCode": "goods",
|
||||
"operations": [
|
||||
"add",
|
||||
"delete",
|
||||
"update",
|
||||
"info",
|
||||
"list",
|
||||
"page"
|
||||
],
|
||||
"descriptorChecksum": "22211efee07c5c1aada51d508d9c053e0d7388ad6fc0da12c95a4d1de2e3ca53",
|
||||
"actions": {
|
||||
"add": "GoodsAdd",
|
||||
"delete": "GoodsDelete",
|
||||
"update": "GoodsUpdate",
|
||||
"info": "GoodsInfo",
|
||||
"list": "GoodsList",
|
||||
"page": "GoodsPage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actionDescriptors": [
|
||||
{
|
||||
"name": "goods.add",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"mutationKind": "insert",
|
||||
"access": "write",
|
||||
"idempotent": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"price"
|
||||
]
|
||||
},
|
||||
"inputChecksum": "c2984884419729416e6cd81e4cddc6d82486b6dde2d9de0ca17533f69f55816a",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"outputChecksum": "bb691ac7eaa19bb231f9da14a80848262965255fb37726ab155fb9804c9a55b1",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:add"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id",
|
||||
"price",
|
||||
"status",
|
||||
"stock",
|
||||
"title"
|
||||
],
|
||||
"select": false
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"aggregateStream": "goods",
|
||||
"sequenceCompleteness": "complete",
|
||||
"idempotencyRequired": true,
|
||||
"events": [
|
||||
{
|
||||
"eventType": "demo.goods.created",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "c3ed1b37c636475284baae530668f10a5b22c07dbd91bc2ed5a12b9b52acbeee",
|
||||
"operation": "upsert"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.deleted",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "61391ce0cf440dba05e89d48b2caccd1670af5fd072e63d4b16623bdc9f74db2",
|
||||
"operation": "delete"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.updated",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "8206c1deed375f3ae71d4455109562c563edefceb75336d7b1a1b51a528c722c",
|
||||
"operation": "upsert"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "goods.batchInfo",
|
||||
"origin": "business",
|
||||
"implementation": "business",
|
||||
"access": "read",
|
||||
"idempotent": false,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ids"
|
||||
],
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"inputChecksum": "b2e0c5fd68adc75e2687c6a1ce01b8270240b42df6511efd6d922ea3242bc264",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"list"
|
||||
],
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputChecksum": "fe644424ef6a9e462b56edc9f70f48bb218783c69469469d4310de48589a0df5",
|
||||
"callerPolicy": {
|
||||
"kind": "module",
|
||||
"allowedModules": [
|
||||
"order"
|
||||
]
|
||||
},
|
||||
"tables": [],
|
||||
"readModels": [],
|
||||
"writes": []
|
||||
},
|
||||
{
|
||||
"name": "goods.delete",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"mutationKind": "delete",
|
||||
"access": "write",
|
||||
"idempotent": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"inputChecksum": "f7411322cb1460ead79eaa8267854f576188c583bfdc621d9cb6b5097991e865",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"affected": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"affected"
|
||||
]
|
||||
},
|
||||
"outputChecksum": "5aa7994f78677fd557c2611cb26b6a1c4518cf4d6ccefa189a5c36af9f229716",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:delete"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id"
|
||||
],
|
||||
"select": false
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"aggregateStream": "goods",
|
||||
"sequenceCompleteness": "complete",
|
||||
"idempotencyRequired": true,
|
||||
"events": [
|
||||
{
|
||||
"eventType": "demo.goods.created",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "c3ed1b37c636475284baae530668f10a5b22c07dbd91bc2ed5a12b9b52acbeee",
|
||||
"operation": "upsert"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.deleted",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "61391ce0cf440dba05e89d48b2caccd1670af5fd072e63d4b16623bdc9f74db2",
|
||||
"operation": "delete"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.updated",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "8206c1deed375f3ae71d4455109562c563edefceb75336d7b1a1b51a528c722c",
|
||||
"operation": "upsert"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "goods.info",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"access": "read",
|
||||
"idempotent": false,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"inputChecksum": "f7411322cb1460ead79eaa8267854f576188c583bfdc621d9cb6b5097991e865",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"outputChecksum": "bb691ac7eaa19bb231f9da14a80848262965255fb37726ab155fb9804c9a55b1",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:info"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id",
|
||||
"price",
|
||||
"status",
|
||||
"stock",
|
||||
"title"
|
||||
],
|
||||
"select": true
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": []
|
||||
},
|
||||
{
|
||||
"name": "goods.list",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"access": "read",
|
||||
"idempotent": false,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"where": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"order": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field",
|
||||
"direction"
|
||||
]
|
||||
},
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"take": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 200
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"inputChecksum": "68074fd84863ef796fc3c99fc87d36c740f4a5bad7134a02b090f7cef6bde883",
|
||||
"outputSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
},
|
||||
"outputChecksum": "2182ab26245d80a9a40216cce7cadbe8217ea12813f1cf536b7a59ba0a4ed4ae",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:list"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id",
|
||||
"price",
|
||||
"status",
|
||||
"stock",
|
||||
"title"
|
||||
],
|
||||
"select": true
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": []
|
||||
},
|
||||
{
|
||||
"name": "goods.page",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"access": "read",
|
||||
"idempotent": false,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"where": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"order": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field",
|
||||
"direction"
|
||||
]
|
||||
},
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 200
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"inputChecksum": "65eb38a87c2692fde220c350f971d8cc91c9ccc28e7bc41221092edaba042a4b",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"price",
|
||||
"stock",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 200
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"page",
|
||||
"size",
|
||||
"total"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"list",
|
||||
"pagination"
|
||||
]
|
||||
},
|
||||
"outputChecksum": "15ee81575d900276aaa2437afa5ee6f41321e5b00ab58706322e5d5c0809bf8d",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:page"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id",
|
||||
"price",
|
||||
"status",
|
||||
"stock",
|
||||
"title"
|
||||
],
|
||||
"select": true
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": []
|
||||
},
|
||||
{
|
||||
"name": "goods.releaseStock",
|
||||
"origin": "business",
|
||||
"implementation": "business",
|
||||
"access": "write",
|
||||
"idempotent": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"goodsId",
|
||||
"quantity",
|
||||
"orderNo"
|
||||
],
|
||||
"properties": {
|
||||
"goodsId": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"orderNo": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"minLength": 1
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"inputChecksum": "c363d259b5108d069650a2554309eb3eb165d98723596595958e00acf8410abe",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"goodsId",
|
||||
"currentStock"
|
||||
],
|
||||
"properties": {
|
||||
"goodsId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"currentStock": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputChecksum": "a632b9f89518c3c17861454b74173a4880d5e8482639d3ff7f761aee4d061bd9",
|
||||
"callerPolicy": {
|
||||
"kind": "module",
|
||||
"allowedModules": [
|
||||
"order"
|
||||
]
|
||||
},
|
||||
"tables": [],
|
||||
"readModels": [],
|
||||
"writes": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"aggregateStream": "goods",
|
||||
"sequenceCompleteness": "complete",
|
||||
"idempotencyRequired": true,
|
||||
"events": [
|
||||
{
|
||||
"eventType": "demo.goods.stockReserved",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "4d8a47d64f7d9fa43e0253228d05a2bb28db7efe3181e96d58dbff2163b9b752",
|
||||
"operation": "upsert"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "goods.reserveStock",
|
||||
"origin": "business",
|
||||
"implementation": "business",
|
||||
"access": "write",
|
||||
"idempotent": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"goodsId",
|
||||
"quantity"
|
||||
],
|
||||
"properties": {
|
||||
"goodsId": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"inputChecksum": "6fbfa4d2324c4c0bdc621cbfd256353284e27495f59f47231b7d897b82a4e7b6",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"goodsId",
|
||||
"remainingStock"
|
||||
],
|
||||
"properties": {
|
||||
"goodsId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"remainingStock": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"outputChecksum": "545bd8b0bf6eb8670b21497236038187ee0f1df1544a318b16a399458fcb796e",
|
||||
"callerPolicy": {
|
||||
"kind": "module",
|
||||
"allowedModules": [
|
||||
"order"
|
||||
]
|
||||
},
|
||||
"tables": [],
|
||||
"readModels": [],
|
||||
"writes": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"aggregateStream": "goods",
|
||||
"sequenceCompleteness": "complete",
|
||||
"idempotencyRequired": true,
|
||||
"events": [
|
||||
{
|
||||
"eventType": "demo.goods.stockReserved",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "4d8a47d64f7d9fa43e0253228d05a2bb28db7efe3181e96d58dbff2163b9b752",
|
||||
"operation": "upsert"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "goods.update",
|
||||
"origin": "table_crud",
|
||||
"implementation": "runtime_managed",
|
||||
"mutationKind": "update",
|
||||
"access": "write",
|
||||
"idempotent": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 120
|
||||
},
|
||||
"price": {
|
||||
"type": "string",
|
||||
"pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
|
||||
},
|
||||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"minProperties": 2
|
||||
},
|
||||
"inputChecksum": "ced49f501c83dd1dce9725c913a1761566828004c851779a5db85ddd2bb9123c",
|
||||
"outputSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"affected": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"affected"
|
||||
]
|
||||
},
|
||||
"outputChecksum": "5aa7994f78677fd557c2611cb26b6a1c4518cf4d6ccefa189a5c36af9f229716",
|
||||
"callerPolicy": {
|
||||
"kind": "permission",
|
||||
"permission": "demo:goods:update"
|
||||
},
|
||||
"tables": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"fields": [
|
||||
"id",
|
||||
"price",
|
||||
"status",
|
||||
"stock",
|
||||
"title"
|
||||
],
|
||||
"select": false
|
||||
}
|
||||
],
|
||||
"readModels": [],
|
||||
"writes": [
|
||||
{
|
||||
"tableCode": "goods",
|
||||
"aggregateStream": "goods",
|
||||
"sequenceCompleteness": "complete",
|
||||
"idempotencyRequired": true,
|
||||
"events": [
|
||||
{
|
||||
"eventType": "demo.goods.created",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "c3ed1b37c636475284baae530668f10a5b22c07dbd91bc2ed5a12b9b52acbeee",
|
||||
"operation": "upsert"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.deleted",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "61391ce0cf440dba05e89d48b2caccd1670af5fd072e63d4b16623bdc9f74db2",
|
||||
"operation": "delete"
|
||||
},
|
||||
{
|
||||
"eventType": "demo.goods.updated",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "8206c1deed375f3ae71d4455109562c563edefceb75336d7b1a1b51a528c722c",
|
||||
"operation": "upsert"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eventHandlerDescriptors": [
|
||||
{
|
||||
"eventType": "saas.saga.compensation.completed",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "2919921c79a7795d6408b202d74aa0fb4b12ec56e1cec7de65eb95bbeeef2c5e",
|
||||
"tables": [
|
||||
"__saas_rpc_saga",
|
||||
"__saas_rpc_saga_step"
|
||||
],
|
||||
"maxAttempts": 10
|
||||
},
|
||||
{
|
||||
"eventType": "saas.saga.compensation.failed",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "b7c7313c221e395c42e45e9480205a673108bdc395430bec892a9dafa39807f3",
|
||||
"tables": [
|
||||
"__saas_rpc_saga",
|
||||
"__saas_rpc_saga_step"
|
||||
],
|
||||
"maxAttempts": 10
|
||||
},
|
||||
{
|
||||
"eventType": "saas.saga.step.completed",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "9d3acfdac4e5713672063f7e9f9b5b0b09487c59238d89dbe8d7145c6a09f1bd",
|
||||
"tables": [
|
||||
"__saas_rpc_saga",
|
||||
"__saas_rpc_saga_step"
|
||||
],
|
||||
"maxAttempts": 10
|
||||
},
|
||||
{
|
||||
"eventType": "saas.saga.step.failed",
|
||||
"eventVersion": 1,
|
||||
"eventContractChecksum": "8dd17e8104b78fa3ece7375402782dee38e121a87c91185658633fabaac8eba0",
|
||||
"tables": [
|
||||
"__saas_rpc_saga",
|
||||
"__saas_rpc_saga_step"
|
||||
],
|
||||
"maxAttempts": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
2044
src/saas/generated/demo/saas-schema.lock.json
Normal file
2044
src/saas/generated/demo/saas-schema.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
1
src/saas/generated/index.ts
Normal file
1
src/saas/generated/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './demo';
|
||||
1
src/service/.gitkeep
Normal file
1
src/service/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
118
src/service/goods.ts
Normal file
118
src/service/goods.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import {
|
||||
InjectSaasCrud,
|
||||
SaasAction,
|
||||
SaasActionContext,
|
||||
SaasBusinessReject,
|
||||
SaasCrudCaller,
|
||||
SaasCrudService,
|
||||
SaasCrudServiceBase,
|
||||
} from '@cool-midway/module-runtime';
|
||||
import {
|
||||
Actions,
|
||||
Crud,
|
||||
Events,
|
||||
type GoodsBatchInfoInput,
|
||||
type GoodsBatchInfoOutput,
|
||||
type GoodsInfoOutput,
|
||||
type GoodsReleaseStockInput,
|
||||
type GoodsReleaseStockOutput,
|
||||
type GoodsReserveStockInput,
|
||||
type GoodsReserveStockOutput,
|
||||
} from '../saas/generated';
|
||||
|
||||
@Provide()
|
||||
@SaasCrudService(Crud.Goods)
|
||||
export class GoodsService extends SaasCrudServiceBase<typeof Crud.Goods> {
|
||||
@InjectSaasCrud(Crud.Goods)
|
||||
private readonly goods!: SaasCrudCaller<typeof Crud.Goods>;
|
||||
|
||||
@Inject()
|
||||
private readonly saasActionContext!: SaasActionContext;
|
||||
|
||||
@SaasAction(Actions.GoodsBatchInfo)
|
||||
async batchInfo(
|
||||
input: GoodsBatchInfoInput
|
||||
): Promise<GoodsBatchInfoOutput> {
|
||||
const list: GoodsInfoOutput[] = [];
|
||||
for (const id of input.ids) {
|
||||
const goods = await this.findGoods(id);
|
||||
if (goods) list.push(goods);
|
||||
}
|
||||
return { list };
|
||||
}
|
||||
|
||||
@SaasAction(Actions.GoodsReserveStock)
|
||||
async reserveStock(
|
||||
input: GoodsReserveStockInput
|
||||
): Promise<GoodsReserveStockOutput> {
|
||||
const goods = await this.requireGoods(input.goodsId);
|
||||
if (goods.status !== 1) {
|
||||
throw new SaasBusinessReject(
|
||||
'GOODS_UNAVAILABLE',
|
||||
`商品 ${input.goodsId} 当前不可售`
|
||||
);
|
||||
}
|
||||
if (goods.stock < input.quantity) {
|
||||
throw new SaasBusinessReject(
|
||||
'INSUFFICIENT_STOCK',
|
||||
`商品 ${input.goodsId} 库存不足`,
|
||||
{ available: goods.stock, requested: input.quantity }
|
||||
);
|
||||
}
|
||||
|
||||
const remainingStock = goods.stock - input.quantity;
|
||||
this.saasActionContext.raise(Events.DemoGoodsStockReserved, {
|
||||
goodsId: input.goodsId,
|
||||
quantity: input.quantity,
|
||||
remainingStock,
|
||||
});
|
||||
await this.updateStock(input.goodsId, remainingStock);
|
||||
return { goodsId: input.goodsId, remainingStock };
|
||||
}
|
||||
|
||||
@SaasAction(Actions.GoodsReleaseStock)
|
||||
async releaseStock(
|
||||
input: GoodsReleaseStockInput
|
||||
): Promise<GoodsReleaseStockOutput> {
|
||||
const goods = await this.requireGoods(input.goodsId);
|
||||
const currentStock = goods.stock + input.quantity;
|
||||
|
||||
this.saasActionContext.raise(Events.DemoGoodsStockReserved, {
|
||||
goodsId: input.goodsId,
|
||||
quantity: input.quantity,
|
||||
remainingStock: currentStock,
|
||||
});
|
||||
await this.updateStock(input.goodsId, currentStock);
|
||||
return { goodsId: input.goodsId, currentStock };
|
||||
}
|
||||
|
||||
private async findGoods(goodsId: number): Promise<GoodsInfoOutput | null> {
|
||||
const rows = await this.goods.list({
|
||||
where: { id: String(goodsId) },
|
||||
take: 1,
|
||||
});
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
private async requireGoods(goodsId: number): Promise<GoodsInfoOutput> {
|
||||
const goods = await this.findGoods(goodsId);
|
||||
if (!goods) {
|
||||
throw new SaasBusinessReject(
|
||||
'GOODS_NOT_FOUND',
|
||||
`商品 ${goodsId} 不存在`
|
||||
);
|
||||
}
|
||||
return goods;
|
||||
}
|
||||
|
||||
private async updateStock(goodsId: number, stock: number): Promise<void> {
|
||||
const result = await this.goods.update({ id: String(goodsId), stock });
|
||||
if (result.affected !== 1) {
|
||||
throw new SaasBusinessReject(
|
||||
'GOODS_STOCK_UPDATE_FAILED',
|
||||
`商品 ${goodsId} 库存更新失败`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user