inanis 0.0.7-beta.13 → 0.0.7-beta.15

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.
@@ -144,6 +144,7 @@ export class Method {
144
144
  if (event.headers['x-warm-up'] === '1') {
145
145
  console.log('warm-up:', (new Date()).toISOString());
146
146
  return resolve({
147
+ headers,
147
148
  statusCode: 200,
148
149
  body: JSON.stringify({
149
150
  message: 'warm-up'
@@ -307,7 +308,8 @@ export class Method {
307
308
  for (let key in attrs) {
308
309
  const doc = docs.query?.[key] || {};
309
310
  const schema = attrs[key];
310
- const isOptional = schema.isOptional();
311
+ const isNumber = schema.safeParse(0).success || schema.type === 'number';
312
+ const isOptional = schema.safeParse(null).success || schema.safeParse(undefined).success;
311
313
  output.parameters.push({
312
314
  in: 'query',
313
315
  name: key,
@@ -315,7 +317,7 @@ export class Method {
315
317
  example: doc.example,
316
318
  required: !isOptional,
317
319
  schema: {
318
- type: 'string'
320
+ type: isNumber ? 'number' : 'string'
319
321
  }
320
322
  });
321
323
  }
@@ -30,6 +30,9 @@ class OpenApiReader {
30
30
  if (data.enum) {
31
31
  output.enum = data.enum;
32
32
  }
33
+ if (data.nullable) {
34
+ output.type = [data.type, 'null'];
35
+ }
33
36
  if (data.description) {
34
37
  output.description = descBeautify(`
35
38
  ${data.description}
@@ -47,6 +50,9 @@ class OpenApiReader {
47
50
  };
48
51
  for (let key in data.properties) {
49
52
  let property = data.properties[key];
53
+ if (property.nullable && Array.isArray(output.required)) {
54
+ output.required = output.required.filter(e => e !== key);
55
+ }
50
56
  if (output.properties) {
51
57
  output.properties[key] = this.schemaToJsonSchema(property);
52
58
  }
@@ -141,7 +141,7 @@ export class DocumentDbManager extends Hook {
141
141
  AttributeName: 'expiredAt'
142
142
  };
143
143
  }
144
- for (let [key, value] of Object.entries(table.params.indexs)) {
144
+ for (let [key, value] of Object.entries(table.params.indexes)) {
145
145
  const attr = value;
146
146
  keys.push(attr.primaryKey);
147
147
  const KeySchema = [
@@ -69,7 +69,7 @@ export class DynamoDbTable {
69
69
  };
70
70
  }
71
71
  async getByIndexId(indexName, primary, secondary) {
72
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
72
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
73
73
  let KeyConditionExpression = '#P = :P';
74
74
  let ExpressionAttributeNames = {
75
75
  '#P': primaryKey
@@ -95,7 +95,7 @@ export class DynamoDbTable {
95
95
  return Items[0];
96
96
  }
97
97
  async listByIndexId(indexName, primary, secondary, options) {
98
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
98
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
99
99
  let params = {
100
100
  Limit: options.limit,
101
101
  TableName: this.tableName,
@@ -152,7 +152,7 @@ export class DynamoDbTable {
152
152
  return result;
153
153
  }
154
154
  async listByIndexIdWithTime(indexName, primary, secondary, before, options) {
155
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
155
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
156
156
  let params = {
157
157
  Limit: options.limit,
158
158
  TableName: this.tableName,
@@ -193,7 +193,7 @@ export class DynamoDbTable {
193
193
  return result;
194
194
  }
195
195
  async betweenByIndexId(indexName, primary, secondary, options) {
196
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
196
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
197
197
  let params = {
198
198
  Limit: options.limit,
199
199
  TableName: this.tableName,
@@ -230,7 +230,7 @@ export class DynamoDbTable {
230
230
  return Count;
231
231
  }
232
232
  async countByIndexId(indexName, primary) {
233
- let { primaryKey } = this.params.indexs[indexName];
233
+ let { primaryKey } = this.params.indexes[indexName];
234
234
  let params = {
235
235
  Select: 'COUNT',
236
236
  TableName: this.tableName,
@@ -72,7 +72,7 @@ export class MongoCollection {
72
72
  };
73
73
  }
74
74
  async getByIndexId(indexName, primary, secondary) {
75
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
75
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
76
76
  let { items } = await this.find({
77
77
  [primaryKey]: primary,
78
78
  [secondaryKey]: secondary
@@ -82,7 +82,7 @@ export class MongoCollection {
82
82
  return items[0];
83
83
  }
84
84
  async listByIndexId(indexName, primary, secondary, options) {
85
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
85
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
86
86
  let { nextKey, items } = await this.find({
87
87
  [primaryKey]: primary,
88
88
  [secondaryKey]: secondary
@@ -116,7 +116,7 @@ export class MongoCollection {
116
116
  };
117
117
  }
118
118
  async betweenByIndexId(indexName, primary, secondary, options) {
119
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
119
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
120
120
  let { items, nextKey } = await this.find({
121
121
  [primaryKey]: primary,
122
122
  [secondaryKey]: {
@@ -141,7 +141,7 @@ export class MongoCollection {
141
141
  };
142
142
  }
143
143
  async listByIndexIdWithTime(indexName, primary, secondary, before, options) {
144
- let { primaryKey, secondaryKey } = this.params.indexs[indexName];
144
+ let { primaryKey, secondaryKey } = this.params.indexes[indexName];
145
145
  let { items, nextKey } = await this.find({
146
146
  [primaryKey]: primary,
147
147
  [secondaryKey]: before ? { $lt: secondary } : { $gt: secondary }
@@ -158,7 +158,7 @@ export class MongoCollection {
158
158
  return count;
159
159
  }
160
160
  async countByIndexId(indexName, primary) {
161
- let { primaryKey } = this.params.indexs[indexName];
161
+ let { primaryKey } = this.params.indexes[indexName];
162
162
  let count = await this.collection.countDocuments({
163
163
  [primaryKey]: primary
164
164
  });
@@ -18,7 +18,7 @@ export type AttrToCalculate<T> = {
18
18
  };
19
19
  export type TableParams<T = any, ID = any, PK = any, SK = any> = {
20
20
  ttl?: number;
21
- indexs: ID;
21
+ indexes: ID;
22
22
  tableName: string;
23
23
  primaryKey: PK;
24
24
  secondaryKey: SK;
@@ -58,6 +58,8 @@ export declare class DataModel<T extends DataModelConfig<any, any, any>, D exten
58
58
  data: D;
59
59
  constructor(config: T, data: T['_initData']);
60
60
  getPublic(): Pick<D, PublicKey>;
61
+ copy(): this;
62
+ from(source: Partial<D> | DataModel<T, D, PublicKey>): void;
61
63
  save(reader?: (_data: D) => Partial<D>): Promise<void>;
62
64
  remove(): Promise<void>;
63
65
  }
@@ -96,6 +96,15 @@ export class DataModel {
96
96
  }
97
97
  return output;
98
98
  }
99
+ copy() {
100
+ const newData = structuredClone(this.data);
101
+ const model = new DataModel(this._config, newData);
102
+ return model;
103
+ }
104
+ from(source) {
105
+ const data = source instanceof DataModel ? source.data : source;
106
+ Object.assign(this.data, data);
107
+ }
99
108
  async save(reader = data => data) {
100
109
  const table = this._config.table;
101
110
  const params = table.params;
@@ -15,6 +15,10 @@ type Events = {
15
15
  after: any;
16
16
  };
17
17
  export declare class StreamRouterContext<Path extends string = any, Body extends Record<string, any> = any, Query extends Record<string, any> = any, Chunk extends Record<string, any> = any, Messages extends FailData = any> {
18
+ _isEnd: boolean;
19
+ _emitter: Event<{
20
+ close: Record<string, unknown>;
21
+ }>;
18
22
  ip: string;
19
23
  meta: Record<any, any>;
20
24
  mode: RunningMode;
@@ -31,13 +35,9 @@ export declare class StreamRouterContext<Path extends string = any, Body extends
31
35
  interaction: Interaction;
32
36
  interactionStepMessage: string;
33
37
  methodStream: ResponseStream;
34
- _isEnd: boolean;
35
- _emitter: Event<{
36
- lostConnection: Record<string, unknown>;
37
- }>;
38
38
  constructor(method: Method);
39
+ get on(): typeof this._emitter.on;
39
40
  static fromAwsLambda(method: Method, event: APIGatewayEvent, responseStream: ResponseStream): Promise<StreamRouterContext<any, any, any, any, any>>;
40
- get on(): any;
41
41
  _validate(): void;
42
42
  isEnded(): boolean;
43
43
  step(name: string): void;
@@ -5,6 +5,8 @@ import { rootException } from '../exception.js';
5
5
  import { ValidationError } from '../utils/errors.js';
6
6
  const exception = rootException.checkout('Api/Context');
7
7
  export class StreamRouterContext {
8
+ _isEnd = false;
9
+ _emitter = new Event();
8
10
  ip = '';
9
11
  meta = {};
10
12
  mode = 'lambda';
@@ -21,8 +23,6 @@ export class StreamRouterContext {
21
23
  interaction;
22
24
  interactionStepMessage = '';
23
25
  methodStream;
24
- _isEnd = false;
25
- _emitter = new Event();
26
26
  constructor(method) {
27
27
  this.method = method;
28
28
  this.interaction = new Interaction({
@@ -35,11 +35,14 @@ export class StreamRouterContext {
35
35
  // 綁定協助解構
36
36
  const methods = Object.getOwnPropertyNames(StreamRouterContext.prototype);
37
37
  for (let key of methods) {
38
- if (key !== 'constructor' && typeof this[key] === 'function') {
38
+ if (key !== 'on' && key !== 'constructor' && typeof this[key] === 'function') {
39
39
  this[key] = this[key].bind(this);
40
40
  }
41
41
  }
42
42
  }
43
+ get on() {
44
+ return this._emitter.on.bind(this._emitter);
45
+ }
43
46
  static async fromAwsLambda(method, event, responseStream) {
44
47
  const context = new StreamRouterContext(method);
45
48
  const xForwardedFor = event.headers['X-Forwarded-For']; // cloudfront
@@ -54,11 +57,11 @@ export class StreamRouterContext {
54
57
  context.language = event.headers?.['Accept-Language'] || '';
55
58
  context.methodStream = responseStream;
56
59
  context.body = json.nonStrictJSONParse(event.body || '{}');
60
+ responseStream.on('close', () => {
61
+ context._emitter.emit('close', {});
62
+ });
57
63
  return context;
58
64
  }
59
- get on() {
60
- return this._emitter.on.bind(this._emitter);
61
- }
62
65
  _validate() {
63
66
  try {
64
67
  if (this.method.verify.body) {
@@ -4,9 +4,8 @@ import { exportOpenApi } from './export-open-api.js';
4
4
  import { Router, RouterParams, Methods } from './router.js';
5
5
  import { AnyError } from '../utils/errors.js';
6
6
  import { Handler } from 'aws-lambda';
7
- export interface ResponseStream {
8
- write(data: string | boolean): void;
9
- end(): void;
7
+ import { Writable } from 'stream';
8
+ export interface ResponseStream extends Writable {
10
9
  setContentType(type: string): void;
11
10
  }
12
11
  export type FailData = Record<string, {
@@ -35,9 +34,9 @@ export declare class StreamAPIManager extends Event<Channels> {
35
34
  routers: Router[];
36
35
  constructor(params: Params);
37
36
  _injectInanis(inanis: Inanis): void;
38
- createRoute<Path extends string, Fails extends FailData, Headers extends string>(params: RouterParams<Path, Fails, Headers>): Router<Path, Fails, Headers>;
37
+ createRoute<Path extends string, Headers extends string>(params: RouterParams<Path, Headers>): Router<Path, Headers>;
39
38
  eachMethods(cb: (_route: Router, _method: keyof Methods) => void): void;
40
- getRouter(path: string): Router<any, any, string>;
39
+ getRouter(path: string): Router<any, string>;
41
40
  getMethod(path: string, method: keyof Methods): import("./method.js").Method<any, any, any, any, any>;
42
41
  get outputs(): {
43
42
  toTypes: () => Promise<string>;
@@ -5,6 +5,15 @@ import { rootException } from '../exception.js';
5
5
  import { exportOpenApi } from './export-open-api.js';
6
6
  import { Router } from './router.js';
7
7
  import { toRouterName } from '../utils/string.js';
8
+ import { streamifyResponse, isInAWS } from 'lambda-stream';
9
+ if (isInAWS() === false) {
10
+ global['awslambda'] = {
11
+ streamifyResponse,
12
+ HttpResponseStream: {
13
+ from: () => null
14
+ }
15
+ };
16
+ }
8
17
  const exception = rootException.checkout('APIManager');
9
18
  const getLambdaGroup = (manager, method, path) => {
10
19
  let sourcePath = `${method}@${path}`;
@@ -123,6 +132,15 @@ export class StreamAPIManager extends Event {
123
132
  const handlerName = group.handlerName + appendHandleName;
124
133
  if (outputs[handlerName] == null) {
125
134
  outputs[handlerName] = awslambda.streamifyResponse(async (event, responseStream) => {
135
+ const httpResponseMetadata = {
136
+ statusCode: 200,
137
+ headers: {
138
+ 'Content-Type': 'text/event-stream',
139
+ 'Access-Control-Allow-Origin': '*',
140
+ 'Access-Control-Allow-Credentials': 'true'
141
+ }
142
+ };
143
+ responseStream = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata);
126
144
  let callPath = event.requestContext.resourcePath;
127
145
  let isOffline = !!this.params.serverless?.isOffline;
128
146
  if (isOffline) {
@@ -47,7 +47,7 @@ export declare class Method<Path extends string = any, Fails extends FailData =
47
47
  private handler;
48
48
  constructor(router: Router, action: string, params: MethodParams<Path, Body, Query, Chunk, Fails>);
49
49
  private get inanis();
50
- get fullRouters(): Router<any, any, string>[];
50
+ get fullRouters(): Router<any, string>[];
51
51
  get allowHeaders(): string[];
52
52
  run(context: StreamRouterContext): Promise<void>;
53
53
  get outputs(): {
@@ -158,7 +158,8 @@ export class Method {
158
158
  for (let key in attrs) {
159
159
  const doc = docs.query?.[key] || {};
160
160
  const schema = attrs[key];
161
- const isOptional = schema.isOptional();
161
+ const isNumber = schema.safeParse(0).success || schema.type === 'number';
162
+ const isOptional = schema.safeParse(null).success || schema.safeParse(undefined).success;
162
163
  output.parameters.push({
163
164
  in: 'query',
164
165
  name: key,
@@ -166,7 +167,7 @@ export class Method {
166
167
  example: doc.example,
167
168
  required: !isOptional,
168
169
  schema: {
169
- type: 'string'
170
+ type: isNumber ? 'number' : 'string'
170
171
  }
171
172
  });
172
173
  }
@@ -11,27 +11,25 @@ export type Methods = {
11
11
  get?: Method;
12
12
  post?: Method;
13
13
  };
14
- export type RouterParams<Path extends string, Fails extends FailData, Headers extends string> = {
14
+ export type RouterParams<Path extends string, Headers extends string> = {
15
15
  path: Path;
16
16
  tags?: string[];
17
- failMessages: Fails;
18
17
  allowHeaders?: Headers[];
19
18
  authorizerExpression?: AuthorizerExpression;
20
19
  };
21
- export declare class Router<Path extends string = any, Fails extends FailData = any, Headers extends string = string> {
20
+ export declare class Router<Path extends string = any, Headers extends string = string> {
22
21
  tags: string[];
23
22
  path: Path;
24
23
  manager: StreamAPIManager;
25
- failMessages: Fails | null;
26
24
  methods: Methods;
27
25
  allowHeaders: string[];
28
26
  authorizerExpression?: AuthorizerExpression;
29
- constructor(manager: StreamAPIManager, params: RouterParams<Path, Fails, Headers>);
27
+ constructor(manager: StreamAPIManager, params: RouterParams<Path, Headers>);
30
28
  get name(): string;
31
29
  get fullTags(): string[];
32
30
  get schema(): typeof definedValidateSchema;
33
31
  get nowAuthorizerExpression(): AuthorizerExpression | null;
34
- export(): Record<string, Router<any, any, string>>;
32
+ export(): Record<string, Router<any, string>>;
35
33
  method<Fails extends FailData, Body extends ValidateCallback<any>, Query extends ValidateCallback<any>, Chunk extends ValidateCallback<any>>(key: keyof Router['methods'], params: MethodParams<Path, Body, Query, Chunk, Fails>): Method;
36
34
  }
37
35
  export {};
@@ -6,14 +6,12 @@ export class Router {
6
6
  tags;
7
7
  path;
8
8
  manager;
9
- failMessages = null;
10
9
  methods = {};
11
10
  allowHeaders;
12
11
  authorizerExpression;
13
12
  constructor(manager, params) {
14
13
  this.path = params.path;
15
14
  this.tags = params.tags || [];
16
- this.failMessages = params.failMessages;
17
15
  this.manager = manager;
18
16
  this.allowHeaders = params.allowHeaders || [];
19
17
  this.authorizerExpression = params.authorizerExpression;
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { ValidateCallback } from './validate.js';
3
- export declare const zodToJsonSchema: (cb: ValidateCallback<any>) => z.core.JSONSchema.JSONSchema;
3
+ export declare const zodToJsonSchema: (cb: ValidateCallback<any>) => z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
4
+ [x: string]: any;
5
+ }, z.core.$strip>>;
4
6
  export type JsonSchema = ReturnType<typeof zodToJsonSchema>;
@@ -1,4 +1,6 @@
1
1
  import { z, toJSONSchema } from 'zod';
2
2
  export const zodToJsonSchema = (cb) => {
3
- return toJSONSchema(z.object(cb(z)));
3
+ return toJSONSchema(z.object(cb(z)), {
4
+ target: 'openapi-3.0'
5
+ });
4
6
  };
@@ -3,5 +3,5 @@ export function definedValidateSchema(cb) {
3
3
  return cb;
4
4
  }
5
5
  export function validate(target, schemaCallback) {
6
- return z.object(schemaCallback(z)).required().parse(target || {});
6
+ return z.object(schemaCallback(z)).parse(target || {});
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "inanis",
3
3
  "type": "module",
4
- "version": "0.0.7-beta.13",
4
+ "version": "0.0.7-beta.15",
5
5
  "description": "Micro Service Best Tools.",
6
6
  "main": "./dist/index.js",
7
7
  "scripts": {
@@ -65,6 +65,7 @@
65
65
  "json-schema-to-typescript": "^15.0.4",
66
66
  "json-to-pretty-yaml": "^1.2.2",
67
67
  "jsonwebtoken": "^9.0.2",
68
+ "lambda-stream": "^0.6.0",
68
69
  "mime": "^4.1.0",
69
70
  "mongodb": "^7.0.0",
70
71
  "node-rsa": "^1.1.1",
@@ -74,7 +75,7 @@
74
75
  "redis": "^5.10.0",
75
76
  "websocket": "^1.0.35",
76
77
  "yaml": "^2.8.2",
77
- "zod": "^4.1.13"
78
+ "zod": "^4.2.1"
78
79
  },
79
80
  "packageManager": "yarn@4.1.1"
80
81
  }