@voxgig/build 3.1.0 → 4.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.
@@ -0,0 +1,132 @@
1
+ /* Copyright © 2022-2026 Voxgig Ltd, MIT License. */
2
+
3
+ // Lambda handler template: one handler source file per lambda service,
4
+ // bootstrapping Seneca via the env folder and delegating to gateway-lambda.
5
+
6
+ import { dive, get, pinify } from '@voxgig/util'
7
+
8
+ import { MsgMetaShape } from '../../shape/msg'
9
+
10
+ import { generate, loadFragment, renderFragment } from './generate'
11
+
12
+
13
+ // Only create if does not exist
14
+ const srv_handler = async (model: any, spec: {
15
+ folder: string
16
+ start?: string
17
+ env?: {
18
+ folder: string
19
+ }
20
+ lang?: string
21
+ tm?: string
22
+ }) => {
23
+ let lang = spec.lang || 'js'
24
+
25
+ const frag = loadFragment('srv_handler.' + lang + '.frag', spec)
26
+
27
+ const files: { name: string, content: string }[] = []
28
+
29
+ Object
30
+ .entries(model.main.srv)
31
+ .filter((entry: any) => entry[1].env?.lambda)
32
+ .forEach((entry: any) => {
33
+ const name = entry[0]
34
+ const srv = entry[1]
35
+
36
+ if ('custom' === srv.env.lambda.kind) {
37
+ return
38
+ }
39
+
40
+ let start = spec.start || 'setup'
41
+ let envFolder = spec.env?.folder || '../../../env/lambda'
42
+
43
+ let handler = 'handler'
44
+ let modify = ''
45
+
46
+ // if (!srv.api.web.active) {
47
+ // if (srv.on && 0 < Object.keys(srv.on).length) {
48
+ // handler = 'eventhandler'
49
+ // modify = `
50
+ // event = {
51
+ // ...event,
52
+ // // TODO: @voxgig/system? util needed to handle this dynamically
53
+ // seneca$: { msg: '${srv.on[Object.keys(srv.on)[0]].events[0].msg}' },
54
+ // }
55
+ // `
56
+ // }
57
+ // }
58
+
59
+
60
+ let prepare = ''
61
+ let complete = ''
62
+
63
+
64
+ dive(model.main.msg.aim[name], 128).map((entry: any) => {
65
+ let path = ['aim', name, ...entry[0]]
66
+ let msgMeta = MsgMetaShape(entry[1])
67
+ let pin = pinify(path)
68
+ if (msgMeta.transport?.queue?.active) {
69
+ complete += `
70
+ seneca.listen({type:'sqs',pin:'${pin}'})`
71
+ }
72
+ })
73
+
74
+
75
+ dive(model.main.srv[name].out, 128).map((entry: any) => {
76
+ let path = entry[0]
77
+ let msgMetaMaybe = get(model.main.msg, path)
78
+ // console.log(name, path, msgMetaMaybe)
79
+ if (msgMetaMaybe?.$) {
80
+ let msgMeta = MsgMetaShape(msgMetaMaybe?.$)
81
+ let pin = pinify(path)
82
+
83
+ if (msgMeta.transport?.queue?.active) {
84
+ complete += `
85
+ seneca.client({type:'sqs',pin:'${pin}'})`
86
+ }
87
+ }
88
+ })
89
+
90
+ let makeGatewayHandler = false
91
+ let onlist = model.main.srv[name].on || {}
92
+ Object.entries(onlist).map((onitem: any) => {
93
+ onitem[1].events.map((event: any) => {
94
+ if ('s3' === event.source) {
95
+ if (!makeGatewayHandler) {
96
+ complete += `
97
+
98
+ const makeGatewayHandler = seneca.export('s3-store/makeGatewayHandler')`
99
+ makeGatewayHandler = true
100
+ }
101
+
102
+ complete += `
103
+ seneca
104
+ .act('sys:gateway,kind:lambda,add:hook,hook:handler', {
105
+ handler: makeGatewayHandler('${event.msg}') })`
106
+ }
107
+ })
108
+ })
109
+
110
+
111
+ const content = renderFragment(frag, {
112
+ envFolder,
113
+ start,
114
+ name,
115
+ complete,
116
+ modify,
117
+ prepare,
118
+ handler,
119
+ })
120
+
121
+ files.push({ name: name + '.' + lang, content })
122
+ })
123
+
124
+ if (0 < files.length) {
125
+ await generate(spec.folder, files)
126
+ }
127
+ }
128
+
129
+
130
+ export {
131
+ srv_handler,
132
+ }
@@ -0,0 +1,201 @@
1
+ /* Copyright © 2022-2026 Voxgig Ltd, MIT License. */
2
+
3
+ // gen/serverless/srv.yml template: one Serverless function definition per
4
+ // lambda-active service, derived from the model.
5
+
6
+ import Fs from 'fs'
7
+ import Path from 'path'
8
+
9
+ import { camelify } from '@voxgig/util'
10
+
11
+ import { CoreConfShape } from '../../shape/conf'
12
+
13
+ import { generate, empty, TM, loadFragment, renderFragment } from './generate'
14
+
15
+
16
+ const srv_yml = async (model: any, spec: {
17
+ folder: string
18
+ tm?: string
19
+ }) => {
20
+
21
+ const core = CoreConfShape(model.main.conf.core)
22
+
23
+ let appname = core.name
24
+ let AppName = camelify(appname)
25
+
26
+ const frag = loadFragment('srv.yml.frag', spec)
27
+
28
+ let srv_yml_prefix_path = Path.join(spec.folder, 'srv.prefix.yml')
29
+ let srv_yml_suffix_path = Path.join(spec.folder, 'srv.suffix.yml')
30
+
31
+ let prefixContent = Fs.existsSync(srv_yml_prefix_path) ?
32
+ Fs.readFileSync(srv_yml_prefix_path) : ''
33
+ let suffixContent = Fs.existsSync(srv_yml_suffix_path) ?
34
+ Fs.readFileSync(srv_yml_suffix_path) : ''
35
+
36
+ let content =
37
+
38
+ prefixContent +
39
+ Object
40
+ .entries(model.main.srv)
41
+ .filter((entry: any) => entry[1].env?.lambda?.active)
42
+ .map((entry: any) => {
43
+ const name = entry[0]
44
+ const srv = entry[1]
45
+
46
+ const lambda = srv.env.lambda
47
+ const handler = lambda.handler
48
+
49
+ // NOTE: gen.custom convention: allows for complete overwrite
50
+ // as a get-out-of-jail
51
+ if (srv.gen?.custom?.lambda?.srv_yml) {
52
+ return srv.gen.custom.lambda.srv_yml
53
+ }
54
+
55
+ const web = srv.api.web
56
+
57
+ let events = ''
58
+
59
+ let onEvents = srv.on
60
+
61
+ if (onEvents) {
62
+ Object.entries(onEvents).forEach((entry: any[]) => {
63
+ // let name = entry[0]
64
+ let spec = entry[1]
65
+
66
+ if ('aws' === spec.provider) {
67
+ spec.events.forEach((ev: any) => {
68
+ if ('s3' === ev.source) {
69
+ events += TM(`
70
+ - s3:
71
+ bucket: ${ev.bucket}
72
+ event: ${ev.event}
73
+ existing: true
74
+ `)
75
+ if (ev.rules) {
76
+ events += TM(`
77
+ rules:
78
+ `)
79
+ if (ev.rules.prefix) {
80
+ events += TM(`
81
+ - prefix: ${ev.rules.prefix}
82
+ `)
83
+ }
84
+
85
+ if (ev.rules.suffix) {
86
+ events += TM(`
87
+ - suffix: ${ev.rules.suffix}
88
+ `)
89
+ }
90
+ }
91
+ }
92
+ else if ('schedule' === ev.source) {
93
+ let entries =
94
+ 'string' === typeof ev.recur ? [ev.recur] : (ev.recur || [])
95
+ let recur = entries.map((entry: string) => {
96
+ let schedule = `
97
+ - schedule:
98
+ rate: ${entry}`
99
+ if (ev.msg) {
100
+ schedule += `
101
+ input:
102
+ msg: ${JSON.stringify(ev.msg)} `
103
+ }
104
+ return schedule
105
+ })
106
+
107
+ events += TM(`
108
+ ${recur}
109
+ `)
110
+
111
+ }
112
+ else if ('sqs' === ev.source) {
113
+ events += TM(`
114
+ - sqs:
115
+ arn:
116
+ Fn::GetAtt:
117
+ - ${ev.qrn}
118
+ - Arn
119
+ batchSize: 1
120
+ `)
121
+
122
+ }
123
+ })
124
+ }
125
+ })
126
+ }
127
+
128
+ // TODO: move to `on`
129
+ if (web.active) {
130
+ let prefix = web.path.prefix
131
+ let suffix = web.path.suffix
132
+ let area = web.path.area
133
+ let method = web.method
134
+ let corsflag = 'false'
135
+ let corsprops = ''
136
+
137
+ let methods = method.split(',')
138
+ // console.log('METHODS', methods)
139
+
140
+ if (web.cors.active) {
141
+ corsflag = 'true'
142
+ if (web.cors.props && !empty(web.cors.props)) {
143
+ corsflag = ''
144
+ corsprops = Object
145
+ .entries(web.cors.props)
146
+ .reduce(((a: any, nv: any) => (
147
+ a += ` ${nv[0]}: ${nv[1]}\n`
148
+ , a)), '')
149
+ }
150
+ }
151
+
152
+ if ('v2' === web.lambda?.gateway) {
153
+ for (let method of methods) {
154
+ events += TM(`
155
+ - httpApi:
156
+ path: "${prefix}${area}${name}${suffix}"
157
+ method: ${method}
158
+ `)
159
+ }
160
+ }
161
+ else {
162
+ for (let method of methods) {
163
+ events += TM(`
164
+ - http:
165
+ path: "${prefix}${area}${name}${suffix}"
166
+ method: ${method}
167
+ cors: ${corsflag}
168
+ ${corsprops}
169
+ `)
170
+ }
171
+ }
172
+ }
173
+
174
+
175
+
176
+ let eventsBlock = ''
177
+ if ('' !== events) {
178
+ eventsBlock = TM(`
179
+ events:
180
+ ${events}
181
+ `)
182
+ }
183
+
184
+ return renderFragment(frag, {
185
+ name,
186
+ handler: handler.path.prefix + name + handler.path.suffix,
187
+ AppName,
188
+ timeout: lambda.timeout,
189
+ memory: lambda.memory || 1024,
190
+ events: eventsBlock,
191
+ })
192
+ }).join('\n\n\n') +
193
+ suffixContent
194
+
195
+ await generate(spec.folder, [{ name: 'srv.yml', content }])
196
+ }
197
+
198
+
199
+ export {
200
+ srv_yml,
201
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/build",
3
- "version": "3.1.0",
3
+ "version": "4.1.0",
4
4
  "main": "dist/build.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/build.d.ts",
@@ -18,7 +18,7 @@
18
18
  "url": "git://github.com/voxgig/build.git"
19
19
  },
20
20
  "scripts": {
21
- "model-s01": "voxgig-model test/s01/model/model.jsonic",
21
+ "model-s01": "voxgig-model test/s01/model/model.aontu",
22
22
  "test": "jest --coverage",
23
23
  "test-some": "jest -t",
24
24
  "test-watch": "jest --coverage --watchAll",
@@ -28,7 +28,13 @@
28
28
  "reset": "npm run clean && npm i && npm run build && npm test",
29
29
  "repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;",
30
30
  "repo-publish": "npm run clean && npm i && npm run repo-publish-quick",
31
- "repo-publish-quick": "npm run build && npm run test && npm run repo-tag && npm publish --registry https://registry.npmjs.org --access=public"
31
+ "repo-publish-quick": "npm run build && npm run test && npm run repo-tag && npm run repo-stage-publish",
32
+ "repo-stage-publish": "aql vault exec --for=npm vxg:pub01 -- npm stage publish --registry https://registry.npmjs.org --access=public",
33
+ "repo-stage-publish-dry": "aql vault exec --dry-run --for=npm vxg:pub01 -- npm stage publish --registry https://registry.npmjs.org --access=public --dry-run",
34
+ "repo-stage-list": "aql vault exec --for=npm vxg:pub01 -- npm stage list",
35
+ "repo-stage-view": "aql vault exec --for=npm vxg:pub01 -- npm stage view",
36
+ "repo-stage-approve": "aql vault exec --for=npm vxg:pub01 -- npm stage approve",
37
+ "repo-stage-reject": "aql vault exec --for=npm vxg:pub01 -- npm stage reject"
32
38
  },
33
39
  "license": "MIT",
34
40
  "files": [
@@ -36,17 +42,21 @@
36
42
  "lib",
37
43
  "dist",
38
44
  "bin",
39
- "LICENSE"
45
+ "LICENSE",
46
+ "tm",
47
+ "env"
40
48
  ],
41
49
  "dependencies": {
42
- "gubu": "^8.0.2",
50
+ "@voxgig/util": "0.5.4",
51
+ "gubu": "8.0.2",
52
+ "jostraca": "0.31.2",
43
53
  "js-yaml": "^4.1.0"
44
54
  },
45
55
  "devDependencies": {
46
56
  "@types/jest": "^29.5.12",
47
57
  "@types/js-yaml": "^4.0.9",
48
- "@types/node": "20.14.10",
49
- "@voxgig/model": "^2.1.0",
58
+ "@types/node": "26.1.2",
59
+ "@voxgig/model": "^9.2.4",
50
60
  "@voxgig/system": "^1.2.0",
51
61
  "esbuild": "^0.23.0",
52
62
  "jest": "^29.7.0",
@@ -0,0 +1,6 @@
1
+ $$resname$$:
2
+ Type: "AWS::SQS::Queue"
3
+ Properties:
4
+ QueueName: '$$queueName$$'
5
+ VisibilityTimeout: $$queueTimeout$$
6
+
@@ -0,0 +1,35 @@
1
+
2
+ Basic$$AppName$$LambdaRole:
3
+ Type: AWS::IAM::Role
4
+ Properties:
5
+ RoleName: Basic$$AppName$$LambdaRole${self:custom.index.BasicLambdaRole,"01"}
6
+ AssumeRolePolicyDocument:
7
+ Version: '2012-10-17'
8
+ Statement:
9
+ - Effect: Allow
10
+ Principal:
11
+ Service:
12
+ - lambda.amazonaws.com
13
+ - events.amazonaws.com
14
+ - ecs-tasks.amazonaws.com
15
+ Action: sts:AssumeRole
16
+ Policies:
17
+ - PolicyName: LambdaServiceAccess
18
+ PolicyDocument:
19
+ Version: '2012-10-17'
20
+ Statement:
21
+ - Effect: Allow
22
+ Action:
23
+ - dynamodb:DescribeTable
24
+ - dynamodb:GetItem
25
+ - dynamodb:PutItem
26
+ - dynamodb:UpdateItem
27
+ - dynamodb:DeleteItem
28
+ - dynamodb:Query
29
+ - dynamodb:Scan
30
+ Resource:
31
+ $$dynamoArns$$
32
+ $$customPolicy$$
33
+ ManagedPolicyArns:
34
+ - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
35
+
@@ -0,0 +1,6 @@
1
+ $$name$$:
2
+ handler: $$handler$$
3
+ role: Basic$$AppName$$LambdaRole
4
+ timeout: $$timeout$$
5
+ memorySize: $$memory$$
6
+ $$events$$
@@ -0,0 +1,17 @@
1
+ const getSeneca = require('$$envFolder$$/$$start$$')
2
+
3
+ function complete(seneca: any) {$$complete$$
4
+ }
5
+
6
+ exports.handler = async (
7
+ event,
8
+ context
9
+ ) => {
10
+ $$modify$$
11
+ let seneca = await getSeneca('$$name$$', complete)
12
+ $$prepare$$
13
+ let handler = seneca.export('gateway-lambda/$$handler$$')
14
+ let res = await handler(event, context)
15
+ return res
16
+ }
17
+
@@ -0,0 +1,17 @@
1
+ import { getSeneca } from '$$envFolder$$/$$start$$'
2
+
3
+ function complete(seneca: any) {$$complete$$
4
+ }
5
+
6
+ exports.handler = async (
7
+ event:any,
8
+ context:any
9
+ ) => {
10
+ $$modify$$
11
+ let seneca = await getSeneca('$$name$$', complete)
12
+ $$prepare$$
13
+ let handler = seneca.export('gateway-lambda/$$handler$$')
14
+ let res = await handler(event, context)
15
+ return res
16
+ }
17
+