@suss/packs 0.36.0 → 0.37.1

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,60 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-activejob';
2
+ import '@suss/framework-activerecord';
3
+ import '@suss/client-aiohttp';
4
+ import '@suss/client-apollo';
5
+ import '@suss/framework-apollo';
6
+ import '@suss/framework-aws-dynamodb';
7
+ import '@suss/framework-aws-eventbridge';
8
+ import '@suss/framework-aws-lambda';
9
+ import '@suss/framework-aws-s3';
10
+ import '@suss/framework-aws-secrets-manager';
11
+ import '@suss/framework-aws-sns';
12
+ import '@suss/framework-aws-sqs';
13
+ import '@suss/framework-aws-ssm';
14
+ import '@suss/client-axios';
15
+ import '@suss/framework-bigquery-python';
16
+ import '@suss/framework-bigquery-ruby';
17
+ import '@suss/framework-bigquery';
18
+ import '@suss/framework-bullmq';
19
+ import '@suss/framework-celery';
20
+ import '@suss/framework-cloudflare-workers';
21
+ import '@suss/framework-drizzle';
22
+ import '@suss/framework-express';
23
+ import '@suss/framework-fabrication';
24
+ import '@suss/framework-factory-bot';
25
+ import '@suss/client-faraday';
26
+ import '@suss/framework-fastapi';
27
+ import '@suss/framework-fastify';
28
+ import '@suss/client-web';
29
+ import '@suss/framework-flask-restx';
30
+ import '@suss/framework-gcs';
31
+ import '@suss/framework-graphql-ruby';
32
+ import '@suss/framework-hono';
33
+ import '@suss/client-httpx';
34
+ import '@suss/framework-minitest';
35
+ import '@suss/framework-mongoose';
36
+ import '@suss/framework-nestjs-graphql';
37
+ import '@suss/framework-nestjs-microservices';
38
+ import '@suss/framework-nestjs-rest';
39
+ import '@suss/client-net-http';
40
+ import '@suss/framework-nextjs';
41
+ import '@suss/runtime-node';
42
+ import '@suss/framework-package-exports';
43
+ import '@suss/framework-pg-ruby';
44
+ import '@suss/framework-pg';
45
+ import '@suss/framework-prisma';
46
+ import '@suss/framework-pytest';
47
+ import '@suss/framework-rails';
48
+ import '@suss/framework-react-query';
49
+ import '@suss/framework-react-router';
50
+ import '@suss/framework-react';
51
+ import '@suss/framework-redis';
52
+ import '@suss/client-requests';
53
+ import '@suss/framework-rspec';
54
+ import '@suss/framework-sidekiq';
55
+ import '@suss/framework-sqlalchemy';
56
+ import '@suss/framework-sqlmodel';
57
+ import '@suss/framework-ts-rest';
58
+ import '@suss/framework-typeorm';
59
+ import '@suss/framework-vitest';
60
+ import '@suss/framework-zustand';
@@ -0,0 +1,48 @@
1
+ // ../framework/activejob/dist/index.js
2
+ import { z } from "zod";
3
+ var optionsSchema = z.object({}).strict();
4
+ function activeJobFramework(_options = {}) {
5
+ return {
6
+ name: "activejob",
7
+ protocol: "activejob",
8
+ discovery: [],
9
+ jobs: [
10
+ {
11
+ type: "jobClasses",
12
+ messageBus: "activejob",
13
+ // A scaffolded app puts ApplicationJob in between. Listing it too
14
+ // covers a run that does not read app/jobs/application_job.rb.
15
+ baseClasses: ["ActiveJob::Base", "ApplicationJob"],
16
+ modules: [],
17
+ performMethod: "perform",
18
+ enqueueMethods: { perform_later: 0, perform_now: 0 },
19
+ optionMethods: ["set"],
20
+ queueDeclarations: [{ call: "queue_as", queue: { at: 0 } }]
21
+ },
22
+ {
23
+ type: "mailers",
24
+ messageBus: "activejob",
25
+ baseClasses: ["ActionMailer::Base", "ApplicationMailer"],
26
+ // `deliver_later!` skips the checks that `deliver_later` runs first.
27
+ enqueueMethods: ["deliver_later", "deliver_later!"],
28
+ optionMethods: ["with"]
29
+ }
30
+ ]
31
+ };
32
+ }
33
+ var declares = {
34
+ kind: "framework",
35
+ package: "@suss/framework-activejob",
36
+ // An app that depends on the Rails gems one at a time lists activejob.
37
+ dependencies: [
38
+ { ecosystem: "rubygems", name: "rails" },
39
+ { ecosystem: "rubygems", name: "activejob" }
40
+ ],
41
+ reads: "ActiveJob jobs and ActionMailer messages (Ruby): each job's perform and each mailer method is a consumer of an activejob channel, and perform_later or deliver_later a send to it."
42
+ };
43
+ var index_default = activeJobFramework;
44
+ export {
45
+ declares,
46
+ index_default as default,
47
+ optionsSchema
48
+ };
@@ -1,56 +1 @@
1
1
  export { declares, default, optionsSchema } from '@suss/framework-activerecord';
2
- import '@suss/client-aiohttp';
3
- import '@suss/client-apollo';
4
- import '@suss/framework-apollo';
5
- import '@suss/framework-aws-dynamodb';
6
- import '@suss/framework-aws-eventbridge';
7
- import '@suss/framework-aws-lambda';
8
- import '@suss/framework-aws-s3';
9
- import '@suss/framework-aws-secrets-manager';
10
- import '@suss/framework-aws-sns';
11
- import '@suss/framework-aws-sqs';
12
- import '@suss/framework-aws-ssm';
13
- import '@suss/client-axios';
14
- import '@suss/framework-bigquery-python';
15
- import '@suss/framework-bigquery-ruby';
16
- import '@suss/framework-bigquery';
17
- import '@suss/framework-cloudflare-workers';
18
- import '@suss/framework-drizzle';
19
- import '@suss/framework-express';
20
- import '@suss/framework-fabrication';
21
- import '@suss/framework-factory-bot';
22
- import '@suss/client-faraday';
23
- import '@suss/framework-fastapi';
24
- import '@suss/framework-fastify';
25
- import '@suss/client-web';
26
- import '@suss/framework-flask-restx';
27
- import '@suss/framework-gcs';
28
- import '@suss/framework-graphql-ruby';
29
- import '@suss/framework-hono';
30
- import '@suss/client-httpx';
31
- import '@suss/framework-minitest';
32
- import '@suss/framework-mongoose';
33
- import '@suss/framework-nestjs-graphql';
34
- import '@suss/framework-nestjs-microservices';
35
- import '@suss/framework-nestjs-rest';
36
- import '@suss/client-net-http';
37
- import '@suss/framework-nextjs';
38
- import '@suss/runtime-node';
39
- import '@suss/framework-package-exports';
40
- import '@suss/framework-pg-ruby';
41
- import '@suss/framework-pg';
42
- import '@suss/framework-prisma';
43
- import '@suss/framework-pytest';
44
- import '@suss/framework-rails';
45
- import '@suss/framework-react-query';
46
- import '@suss/framework-react-router';
47
- import '@suss/framework-react';
48
- import '@suss/framework-redis';
49
- import '@suss/client-requests';
50
- import '@suss/framework-rspec';
51
- import '@suss/framework-sqlalchemy';
52
- import '@suss/framework-sqlmodel';
53
- import '@suss/framework-ts-rest';
54
- import '@suss/framework-typeorm';
55
- import '@suss/framework-vitest';
56
- import '@suss/framework-zustand';
@@ -2,7 +2,7 @@ import {
2
2
  awsLambdaPython,
3
3
  declaresPython,
4
4
  optionsSchema
5
- } from "./chunk-63BQSSQT.js";
5
+ } from "./chunk-SH7CMRMX.js";
6
6
  export {
7
7
  declaresPython as declares,
8
8
  awsLambdaPython as default,
@@ -2,7 +2,7 @@ import {
2
2
  awsLambdaRuby,
3
3
  declaresRuby,
4
4
  optionsSchema
5
- } from "./chunk-63BQSSQT.js";
5
+ } from "./chunk-SH7CMRMX.js";
6
6
  export {
7
7
  declaresRuby as declares,
8
8
  awsLambdaRuby as default,
@@ -2,7 +2,7 @@ import {
2
2
  declares,
3
3
  index_default,
4
4
  optionsSchema
5
- } from "./chunk-63BQSSQT.js";
5
+ } from "./chunk-SH7CMRMX.js";
6
6
  export {
7
7
  declares,
8
8
  index_default as default,
@@ -0,0 +1 @@
1
+ export { declares, default } from '@suss/framework-bullmq';
package/dist/bullmq.js ADDED
@@ -0,0 +1,138 @@
1
+ // ../framework/bullmq/dist/index.js
2
+ import { constructedFrom, messageSends, pack } from "@suss/recognize";
3
+ var BULLMQ = "bullmq";
4
+ var QUEUE = constructedFrom({ from: [BULLMQ], named: ["Queue"] });
5
+ var FLOW_PRODUCER = constructedFrom({
6
+ from: [BULLMQ],
7
+ named: ["FlowProducer"]
8
+ });
9
+ var QUEUE_BUILT_WITH = [
10
+ { argument: { of: [{ to: "receiver" }], at: 0 } }
11
+ ];
12
+ var QUEUE_NAMED_IN_THE_FLOW = [
13
+ { property: ["queueName"] }
14
+ ];
15
+ function sendDeclarations() {
16
+ return [
17
+ messageSends({
18
+ wire: "bullmq",
19
+ client: QUEUE,
20
+ messages: { each: "theInput" },
21
+ channel: QUEUE_BUILT_WITH,
22
+ body: { whole: "message" },
23
+ routingKey: { at: 0 },
24
+ unsettledName: "nothing"
25
+ }).methods({ add: { input: { at: 1 } } }).example('queue.add("welcome", { userId })'),
26
+ messageSends({
27
+ wire: "bullmq",
28
+ client: QUEUE,
29
+ messages: { each: "inTheInput" },
30
+ channel: QUEUE_BUILT_WITH,
31
+ body: "data",
32
+ routingKey: "name",
33
+ unsettledName: "nothing"
34
+ }).methods({ addBulk: { input: { at: 0 } } }).example('queue.addBulk([{ name: "welcome", data: { userId } }])'),
35
+ // A job scheduler adds a job on every tick, from the template it is
36
+ // given here.
37
+ messageSends({
38
+ wire: "bullmq",
39
+ client: QUEUE,
40
+ messages: { each: "theInput" },
41
+ channel: QUEUE_BUILT_WITH,
42
+ body: "data",
43
+ routingKey: "name",
44
+ unsettledName: "nothing"
45
+ }).methods({ upsertJobScheduler: { input: { at: 2 } } }).example(
46
+ 'queue.upsertJobScheduler("nightly", { pattern: "0 0 * * *" }, { name: "digest", data: {} })'
47
+ ),
48
+ messageSends({
49
+ wire: "bullmq",
50
+ client: FLOW_PRODUCER,
51
+ messages: { each: "theInput" },
52
+ channel: QUEUE_NAMED_IN_THE_FLOW,
53
+ body: "data",
54
+ routingKey: "name",
55
+ unsettledName: "nothing"
56
+ }).methods({ add: { input: { at: 0 } } }).example(
57
+ 'flow.add({ name: "render", queueName: "renders", data: { id } })'
58
+ ),
59
+ messageSends({
60
+ wire: "bullmq",
61
+ client: FLOW_PRODUCER,
62
+ messages: { each: "inTheInput" },
63
+ channel: QUEUE_NAMED_IN_THE_FLOW,
64
+ body: "data",
65
+ routingKey: "name",
66
+ unsettledName: "nothing"
67
+ }).methods({ addBulk: { input: { at: 0 } } }).example(
68
+ 'flow.addBulk([{ name: "render", queueName: "renders", data: { id } }])'
69
+ )
70
+ ];
71
+ }
72
+ function bullmqFramework() {
73
+ const sends = pack("bullmq", sendDeclarations(), {
74
+ languages: ["typescript", "javascript"],
75
+ recognizedAs: "@suss/framework-bullmq"
76
+ });
77
+ return {
78
+ ...sends,
79
+ discovery: [
80
+ {
81
+ kind: "consumer",
82
+ match: {
83
+ type: "callbackArgument",
84
+ importModule: BULLMQ,
85
+ importName: "Worker",
86
+ callbackPosition: 1
87
+ },
88
+ binding: {
89
+ semantics: "message-bus",
90
+ messageBus: "bullmq",
91
+ channel: { from: "callArgument", position: 0 }
92
+ },
93
+ requiresImport: [BULLMQ]
94
+ }
95
+ ],
96
+ terminals: [
97
+ // A processor's return value is the job's result, which BullMQ
98
+ // stores and nothing here pairs yet.
99
+ {
100
+ kind: "return",
101
+ match: { type: "returnStatement", excludeCallReturns: false },
102
+ extraction: {}
103
+ },
104
+ {
105
+ kind: "throw",
106
+ match: { type: "throwExpression" },
107
+ extraction: {}
108
+ },
109
+ // Most processors return nothing, and a job completes when its
110
+ // processor runs off the end.
111
+ {
112
+ kind: "return",
113
+ match: { type: "functionFallthrough" },
114
+ extraction: {}
115
+ }
116
+ ],
117
+ // The job is not the message: what the add sent is `job.data`, so
118
+ // the job keeps a role of its own rather than the one a message has.
119
+ inputMapping: {
120
+ type: "positionalParams",
121
+ params: [
122
+ { position: 0, role: "job" },
123
+ { position: 1, role: "token" }
124
+ ]
125
+ }
126
+ };
127
+ }
128
+ var declares = {
129
+ kind: "framework",
130
+ package: "@suss/framework-bullmq",
131
+ dependencies: [{ ecosystem: "npm", name: "bullmq" }],
132
+ reads: "BullMQ workers and queues: each worker's processor is a consumer on its queue, and each add to a queue is a message send on it."
133
+ };
134
+ var index_default = bullmqFramework;
135
+ export {
136
+ declares,
137
+ index_default as default
138
+ };
@@ -0,0 +1 @@
1
+ export { declares, default } from '@suss/framework-celery';
package/dist/celery.js ADDED
@@ -0,0 +1,53 @@
1
+ // ../framework/celery/dist/index.js
2
+ function celeryJobQueue() {
3
+ return {
4
+ messageBus: "celery",
5
+ module: "celery",
6
+ taskDecorators: ["task", "shared_task"],
7
+ nameKeyword: "name",
8
+ queueKeyword: "queue",
9
+ defaultName: "modulePath",
10
+ enqueueMethods: [
11
+ { method: "delay", arguments: { type: "spread" } },
12
+ {
13
+ method: "apply_async",
14
+ arguments: {
15
+ type: "packed",
16
+ args: { argument: 0, keyword: "args" },
17
+ kwargs: { argument: 1, keyword: "kwargs" }
18
+ }
19
+ }
20
+ ],
21
+ sendByName: [
22
+ {
23
+ method: "send_task",
24
+ name: { argument: 0, keyword: "name" },
25
+ arguments: {
26
+ type: "packed",
27
+ args: { argument: 1, keyword: "args" },
28
+ kwargs: { argument: 2, keyword: "kwargs" }
29
+ }
30
+ }
31
+ ],
32
+ apps: ["Celery", "current_app"]
33
+ };
34
+ }
35
+ function celeryFramework() {
36
+ return {
37
+ name: "celery",
38
+ protocol: "celery",
39
+ discovery: [],
40
+ jobQueues: [celeryJobQueue()]
41
+ };
42
+ }
43
+ var declares = {
44
+ kind: "framework",
45
+ package: "@suss/framework-celery",
46
+ dependencies: [{ ecosystem: "pypi", name: "celery" }],
47
+ reads: "Celery tasks (Python). A function under `@app.task` or `@shared_task` is the consumer of its task, and `.delay`, `.apply_async` and `send_task` are sends to it, so a route that enqueues a task reaches what the task does."
48
+ };
49
+ var index_default = celeryFramework;
50
+ export {
51
+ declares,
52
+ index_default as default
53
+ };
@@ -15,7 +15,11 @@ import {
15
15
  readServerlessFunctions,
16
16
  unfollowedStackMessage
17
17
  } from "@suss/manifest-aws";
18
- import { messageBusBinding, unitInvocationBinding } from "@suss/behavioral-ir";
18
+ import {
19
+ graphqlResolverBinding,
20
+ messageBusBinding,
21
+ unitInvocationBinding
22
+ } from "@suss/behavioral-ir";
19
23
  import { constructedFrom, unitInvokes } from "@suss/recognize";
20
24
  var TEMPLATE_NAMES = [
21
25
  "template.yaml",
@@ -34,7 +38,9 @@ function manifestTree(manifestPath) {
34
38
  {
35
39
  path: path.resolve(manifestPath),
36
40
  stackPath: [],
37
- template: translated.functions
41
+ template: translated.functions,
42
+ resourceLines: /* @__PURE__ */ new Map(),
43
+ parameterTargets: /* @__PURE__ */ new Map()
38
44
  }
39
45
  ],
40
46
  unfollowed: []
@@ -364,9 +370,36 @@ function awsLambdaDiscovery() {
364
370
  function lambdaEntryPoints(recognition) {
365
371
  return (filePath) => handlersForFile(filePath).map((entry) => ({
366
372
  name: entry.exportName,
367
- units: [handlerUnit(entry, recognition)]
373
+ units: entryUnits(entry, recognition)
368
374
  }));
369
375
  }
376
+ function entryUnits(entry, recognition) {
377
+ const operations = operationFields(entry);
378
+ const fields = operations.map(
379
+ (field) => resolverUnit(entry, field, recognition)
380
+ );
381
+ const reachedOtherwise = entry.httpRoutes.length > 0 || entry.nonHttpEvents.length > 0 || entry.graphqlFields.length > operations.length;
382
+ return fields.length > 0 && !reachedOtherwise ? fields : [...fields, handlerUnit(entry, recognition)];
383
+ }
384
+ function resolverUnit(entry, field, recognition) {
385
+ return {
386
+ kind: "handler",
387
+ name: `${entry.functionLogicalId}.${entry.exportName}`,
388
+ binding: graphqlResolverBinding({
389
+ transport: "aws-https",
390
+ recognition,
391
+ typeName: field.typeName,
392
+ fieldName: field.fieldName
393
+ }),
394
+ deployableUnit: deployableUnit(entry),
395
+ metadata: {
396
+ [METADATA_NAMESPACE]: {
397
+ handler: entry.handler,
398
+ recognition: "appsync-resolver"
399
+ }
400
+ }
401
+ };
402
+ }
370
403
  function handlerUnit(entry, recognition) {
371
404
  const eventTypes = [
372
405
  ...entry.httpRoutes.map((route) => route.eventType),
@@ -45,6 +45,56 @@ var DEFAULT_BASE_CLASS_NAMES = [
45
45
  "Types::BaseUnion",
46
46
  "Types::BaseScalar"
47
47
  ];
48
+ function orNull(shape) {
49
+ return { type: "union", variants: [shape, { type: "null" }] };
50
+ }
51
+ var PAGE_INFO_FIELDS = {
52
+ hasNextPage: { type: "boolean" },
53
+ hasPreviousPage: { type: "boolean" },
54
+ startCursor: orNull({ type: "text" }),
55
+ endCursor: orNull({ type: "text" })
56
+ };
57
+ var CONNECTION_TYPE = {
58
+ name: "{type}Connection",
59
+ types: {
60
+ "{type}Connection": {
61
+ edges: orNull({
62
+ type: "array",
63
+ items: orNull({ type: "ref", name: "{type}Edge" })
64
+ }),
65
+ nodes: orNull({
66
+ type: "array",
67
+ items: orNull({ type: "ref", name: "{type}" })
68
+ }),
69
+ pageInfo: { type: "ref", name: "PageInfo" }
70
+ },
71
+ "{type}Edge": {
72
+ cursor: { type: "text" },
73
+ node: orNull({ type: "ref", name: "{type}" })
74
+ }
75
+ }
76
+ };
77
+ var COLLECTION_METADATA_FIELDS = {
78
+ currentPage: { type: "number" },
79
+ limitValue: { type: "number" },
80
+ totalCount: { type: "number" },
81
+ totalPages: { type: "number" }
82
+ };
83
+ var COLLECTION_TYPE = {
84
+ name: "{type}Collection",
85
+ types: {
86
+ "{type}Collection": {
87
+ collection: { type: "array", items: { type: "ref", name: "{type}" } },
88
+ metadata: { type: "ref", name: "{metadata_type}" }
89
+ }
90
+ },
91
+ typeArguments: {
92
+ "{metadata_type}": {
93
+ keyword: "metadata_type",
94
+ fallback: "CollectionMetadata"
95
+ }
96
+ }
97
+ };
48
98
  function rootDirectory(options) {
49
99
  if (options.configDirectory === void 0 || path.isAbsolute(options.root)) {
50
100
  return options.root;
@@ -76,8 +126,32 @@ function graphqlRubyFramework(options) {
76
126
  // and the payload comes back in its place.
77
127
  resolverHooks: {
78
128
  methods: ["ready?", "authorized?"],
79
- answerIsListHead: true
129
+ answerIsListHead: true,
130
+ // A bare false from `ready?` resolves the field to null. One from
131
+ // `authorized?` raises, and the schema decides what the client sees.
132
+ refusalRaises: { "authorized?": "GraphQL::UnauthorizedFieldError" }
133
+ },
134
+ typeKeyword: "type",
135
+ typeNameCallName: "graphql_name",
136
+ // A field, and a mutation's or resolver's own type, can be null unless
137
+ // it says `null: false`. The items of a list cannot unless the list
138
+ // type says `[Type, null: true]`.
139
+ nullability: {
140
+ keyword: "null",
141
+ callName: "null",
142
+ nullableByDefault: true,
143
+ listItemsNullableByDefault: false
144
+ },
145
+ typeWrappers: {
146
+ connection_type: CONNECTION_TYPE,
147
+ collection_type: COLLECTION_TYPE
148
+ },
149
+ libraryTypes: {
150
+ PageInfo: PAGE_INFO_FIELDS,
151
+ CollectionMetadata: COLLECTION_METADATA_FIELDS
80
152
  },
153
+ // Returning one of these puts it in `errors[]` in place of the value.
154
+ returnedErrorClassNames: ["GraphQL::ExecutionError"],
81
155
  ancestryRootClassNames: [...GRAPHQL_RUBY_ROOT_CLASS_NAMES],
82
156
  requiredKeyword: "required",
83
157
  // graphql-ruby registers an argument as required unless the call
@@ -133,7 +207,7 @@ var declares = {
133
207
  kind: "framework",
134
208
  package: "@suss/framework-graphql-ruby",
135
209
  dependencies: [{ ecosystem: "rubygems", name: "graphql" }],
136
- reads: "graphql-ruby class-based field DSL (Ruby). The pack follows `mutation:` and `resolver:` wiring to the referenced class, wherever the run declares it, and reads its arguments, its payload, its `resolve` method and the `ready?` and `authorized?` hooks that run first. It follows `resolver_method:` to the type's own method. It also records the model a resolver reads through `dataloader`, when a storage pack in the run recognizes that model.",
210
+ reads: "graphql-ruby class-based field DSL (Ruby). The pack follows `mutation:` and `resolver:` wiring to the referenced class, wherever the run declares it, and reads its arguments, its payload, its `resolve` method and the `ready?` and `authorized?` hooks that run first. It follows `resolver_method:` to the type's own method. Each path through that method records what the field returns, from its declared type with null where the schema allows it and the object type's fields, and the errors it raises or returns. It also records the model a resolver reads through `dataloader`, when a storage pack in the run recognizes that model.",
137
211
  configuration: {
138
212
  file: "suss.graphql-ruby.json",
139
213
  example: { root: "app/graphql" },
package/dist/node.js CHANGED
@@ -7,8 +7,10 @@ import {
7
7
  import {
8
8
  declarationsBehind,
9
9
  dottedPathOf,
10
+ fallbackOf,
10
11
  functionTargetOf,
11
12
  isDefaultedAt,
13
+ scalarValueOf,
12
14
  stringValueOf,
13
15
  symbolBehind,
14
16
  toFunctionRoot
@@ -504,10 +506,11 @@ function directEnvRead(fn, parameter) {
504
506
  function readsBuildKey(read) {
505
507
  return read.object === IMPORT_META_ENV && read.name !== null && VITE_BUILD_KEYS.has(read.name);
506
508
  }
507
- function configReadEffect(read, where) {
509
+ function configReadEffect(read, where, resolution) {
508
510
  if (readsBuildKey(read)) {
509
511
  return opaqueRuntimeRead(readName(read));
510
512
  }
513
+ const fallback = read.defaulted ? defaultValueAt(read.node, resolution) : null;
511
514
  return {
512
515
  type: "interaction",
513
516
  binding: configBinding(where),
@@ -515,16 +518,21 @@ function configReadEffect(read, where) {
515
518
  interaction: {
516
519
  class: "config-read",
517
520
  name: read.name,
518
- defaulted: read.defaulted
521
+ defaulted: read.defaulted,
522
+ ...fallback === null ? {} : { default: fallback }
519
523
  }
520
524
  };
521
525
  }
526
+ function defaultValueAt(read, resolution) {
527
+ const fallback = fallbackOf(read);
528
+ return fallback === null ? null : scalarValueOf(fallback, resolution);
529
+ }
522
530
  function recognizeProcessEnvRead(access, where, resolution) {
523
531
  const reads = envReadsAt(access, resolution);
524
532
  if (reads.length === 0) {
525
533
  return null;
526
534
  }
527
- return reads.map((read) => configReadEffect(read, where));
535
+ return reads.map((read) => configReadEffect(read, where, resolution));
528
536
  }
529
537
  function envVarRecognizer(opts = {}) {
530
538
  return (access, ctx) => recognizeProcessEnvRead(
package/dist/prisma.js CHANGED
@@ -150,6 +150,55 @@ function textOf(file) {
150
150
  return "";
151
151
  }
152
152
  }
153
+ function generatedClientInputs(walked) {
154
+ const roots = /* @__PURE__ */ new Set();
155
+ for (const dir of new Set(walked.map((file) => path3.dirname(file)))) {
156
+ const root = projectRootOf(dir);
157
+ if (root !== null) {
158
+ roots.add(root);
159
+ }
160
+ }
161
+ const inputs = /* @__PURE__ */ new Set();
162
+ for (const root of roots) {
163
+ const project = readProjectAt(root);
164
+ for (const file of project?.readFrom.keys() ?? []) {
165
+ inputs.add(file);
166
+ }
167
+ for (const dir of [...project?.dirs ?? [], ...defaultClientDirs(root)]) {
168
+ for (const file of clientTypeFiles(dir)) {
169
+ inputs.add(file);
170
+ }
171
+ }
172
+ }
173
+ return [...inputs].sort();
174
+ }
175
+ function defaultClientDirs(root) {
176
+ for (let dir = root; ; dir = path3.dirname(dir)) {
177
+ const client = path3.join(dir, "node_modules", ".prisma", "client");
178
+ if (fs3.existsSync(client)) {
179
+ return [client];
180
+ }
181
+ if (path3.dirname(dir) === dir) {
182
+ return [];
183
+ }
184
+ }
185
+ }
186
+ var CLIENT_TYPE_FILE = /(\.d\.[cm]?ts|\.[cm]?ts|\.prisma|^package\.json)$/;
187
+ function clientTypeFiles(dir) {
188
+ let entries;
189
+ try {
190
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
191
+ } catch {
192
+ return [];
193
+ }
194
+ return entries.flatMap((entry) => {
195
+ const file = path3.join(dir, entry.name);
196
+ if (entry.isDirectory()) {
197
+ return entry.name === "runtime" || entry.name === "node_modules" ? [] : clientTypeFiles(file);
198
+ }
199
+ return CLIENT_TYPE_FILE.test(entry.name) ? [file] : [];
200
+ });
201
+ }
153
202
  function isInGeneratedClient(filePath) {
154
203
  return generatedClientDirs(path3.dirname(filePath)).some(
155
204
  (dir) => filePath.startsWith(`${dir}${path3.sep}`)
@@ -774,6 +823,7 @@ function prismaFramework(options = {}) {
774
823
  // `prisma-client` generator writes no schema beside it.
775
824
  generatedModuleMarkers: [GENERATED_CLIENT_MARKER],
776
825
  generatedModuleDirs: generatedClientDirs,
826
+ discoveryInputs: generatedClientInputs,
777
827
  invocationRecognizers: [makeRecognizer(options)],
778
828
  // A tagged template is not an invocation, so the raw chain runs on
779
829
  // the access walk, which visits calls as well.
package/dist/rails.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  symbolValue
30
30
  } from "@suss/adapter-ruby";
31
31
 
32
- // ../ir-core/dist/chunk-7IOGU4OC.js
32
+ // ../ir-core/dist/chunk-OROVMQY2.js
33
33
  import { z as z13 } from "zod";
34
34
  import { z as z12 } from "zod";
35
35
  import { z } from "zod";
@@ -257,7 +257,11 @@ var MessageBusSemanticsSchema = z5.object({
257
257
  "aws_kinesis",
258
258
  "aws_firehose",
259
259
  "gcp_pubsub",
260
+ // Job queues. The header says what their channel is.
260
261
  "bullmq",
262
+ "activejob",
263
+ "sidekiq",
264
+ "celery",
261
265
  "kafka",
262
266
  "nats",
263
267
  "cloudflare-queues",
@@ -273,6 +277,26 @@ var MessageBusSemanticsSchema = z5.object({
273
277
  */
274
278
  channel: z5.string().min(1).nullable()
275
279
  });
280
+ var MESSAGES = { jobQueue: false };
281
+ var JOBS = { jobQueue: true };
282
+ var MESSAGE_BUS_TRAITS = {
283
+ aws_sqs: MESSAGES,
284
+ "aws.sns": MESSAGES,
285
+ s3: MESSAGES,
286
+ eventbridge: MESSAGES,
287
+ aws_kinesis: MESSAGES,
288
+ aws_firehose: MESSAGES,
289
+ gcp_pubsub: MESSAGES,
290
+ bullmq: JOBS,
291
+ activejob: JOBS,
292
+ sidekiq: JOBS,
293
+ celery: JOBS,
294
+ kafka: MESSAGES,
295
+ nats: MESSAGES,
296
+ "cloudflare-queues": MESSAGES,
297
+ "cloudflare-cron": MESSAGES,
298
+ "cloudflare-tail": MESSAGES
299
+ };
276
300
  var messageBusSemantics = defineBoundarySemantics({
277
301
  name: "message-bus",
278
302
  schema: MessageBusSemanticsSchema,
@@ -289,6 +313,8 @@ var messageBusSemantics = defineBoundarySemantics({
289
313
  * the generic unmatched list leaves them out.
290
314
  */
291
315
  reportsUnpairedItself: true,
316
+ consumedAs: ["reads"],
317
+ unsettledJob: (semantics) => semantics.channel === null && MESSAGE_BUS_TRAITS[semantics.messageBus].jobQueue,
292
318
  /**
293
319
  * A channel that is null here can still pair. The dedicated pass
294
320
  * follows environment variable chains before comparing, so a
@@ -1070,6 +1096,7 @@ var runtimeConfigSemantics = defineBoundarySemantics({
1070
1096
  // comes from outside the code.
1071
1097
  leavesTheProcess: true,
1072
1098
  reportsUnpairedItself: false,
1099
+ findingsAreReadsByName: true,
1073
1100
  identityKey: () => null,
1074
1101
  // A reader sees which deployable's environment this is. Code that
1075
1102
  // reads a variable does not say which deployable runs it, so a read
@@ -1415,6 +1442,13 @@ var SuppressionRuleSchema = z22.object({
1415
1442
  * only intent findings about a scenario with that title.
1416
1443
  */
1417
1444
  scenario: z22.string().optional(),
1445
+ /**
1446
+ * The deployments, by name, that a finding about one environment read
1447
+ * is accepted for. The rule takes them off the finding's list, and
1448
+ * the finding stays while any deployment it lists is left. Without
1449
+ * this, a rule covers every deployment.
1450
+ */
1451
+ deployments: z22.array(z22.string().min(1)).min(1).optional(),
1418
1452
  /**
1419
1453
  * "narrow", the default, requires `kind` plus one of `boundary`,
1420
1454
  * `scenario`, `consumer.transitionId` or `provider.transitionId`, so
@@ -2731,6 +2765,7 @@ function railsFramework(options = {}) {
2731
2765
  inflections,
2732
2766
  // An action reads the request through these, and is handed nothing.
2733
2767
  requestAccessors: ["params", "request"],
2768
+ blankToNil: ["presence"],
2734
2769
  // Rails merges the path, the query string and the body into `params`,
2735
2770
  // so an action's read of one cannot be told from the others and all
2736
2771
  // of them are spelled `params`.
@@ -0,0 +1 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-sidekiq';
@@ -0,0 +1,37 @@
1
+ // ../framework/sidekiq/dist/index.js
2
+ import { z } from "zod";
3
+ var optionsSchema = z.object({}).strict();
4
+ function sidekiqFramework(_options = {}) {
5
+ return {
6
+ name: "sidekiq",
7
+ protocol: "sidekiq",
8
+ discovery: [],
9
+ jobs: [
10
+ {
11
+ type: "jobClasses",
12
+ messageBus: "sidekiq",
13
+ baseClasses: [],
14
+ modules: ["Sidekiq::Job", "Sidekiq::Worker"],
15
+ performMethod: "perform",
16
+ // `perform_in` and `perform_at` take the delay or the time first.
17
+ enqueueMethods: { perform_async: 0, perform_in: 1, perform_at: 1 },
18
+ optionMethods: ["set"],
19
+ queueDeclarations: [
20
+ { call: "sidekiq_options", queue: { at: 0, keyword: "queue" } }
21
+ ]
22
+ }
23
+ ]
24
+ };
25
+ }
26
+ var declares = {
27
+ kind: "framework",
28
+ package: "@suss/framework-sidekiq",
29
+ dependencies: [{ ecosystem: "rubygems", name: "sidekiq" }],
30
+ reads: "Sidekiq jobs (Ruby): each job's perform is a consumer of a sidekiq channel, and perform_async, perform_in or perform_at a send to it."
31
+ };
32
+ var index_default = sidekiqFramework;
33
+ export {
34
+ declares,
35
+ index_default as default,
36
+ optionsSchema
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/packs",
3
- "version": "0.36.0",
3
+ "version": "0.37.1",
4
4
  "type": "module",
5
5
  "description": "Every pack suss ships in one install, so it can read Express, Hono, NestJS, FastAPI, Rails, Prisma, OpenAPI and the rest.",
6
6
  "keywords": [
@@ -93,6 +93,14 @@
93
93
  "types": "./dist/bigquery-python.d.ts",
94
94
  "import": "./dist/bigquery-python.js"
95
95
  },
96
+ "./celery": {
97
+ "types": "./dist/celery.d.ts",
98
+ "import": "./dist/celery.js"
99
+ },
100
+ "./bullmq": {
101
+ "types": "./dist/bullmq.d.ts",
102
+ "import": "./dist/bullmq.js"
103
+ },
96
104
  "./cloudflare-workers": {
97
105
  "types": "./dist/cloudflare-workers.d.ts",
98
106
  "import": "./dist/cloudflare-workers.js"
@@ -189,6 +197,14 @@
189
197
  "types": "./dist/rspec.d.ts",
190
198
  "import": "./dist/rspec.js"
191
199
  },
200
+ "./activejob": {
201
+ "types": "./dist/activejob.d.ts",
202
+ "import": "./dist/activejob.js"
203
+ },
204
+ "./sidekiq": {
205
+ "types": "./dist/sidekiq.d.ts",
206
+ "import": "./dist/sidekiq.js"
207
+ },
192
208
  "./factory-bot": {
193
209
  "types": "./dist/factory-bot.d.ts",
194
210
  "import": "./dist/factory-bot.js"
@@ -275,16 +291,16 @@
275
291
  },
276
292
  "dependencies": {
277
293
  "@mrleebo/prisma-ast": "^0.15.0",
278
- "@suss/adapter-python": "0.36.0",
279
- "@suss/adapter-ruby": "0.36.0",
280
- "@suss/adapter-typescript": "0.36.0",
281
- "@suss/behavioral-ir": "0.36.0",
282
- "@suss/contract-graphql": "0.36.0",
283
- "@suss/contract-serverless": "0.36.0",
284
- "@suss/extractor": "0.36.0",
285
- "@suss/manifest-aws": "0.36.0",
286
- "@suss/recognize": "0.36.0",
287
- "@suss/sql": "0.36.0",
294
+ "@suss/adapter-python": "0.37.1",
295
+ "@suss/adapter-ruby": "0.37.1",
296
+ "@suss/adapter-typescript": "0.37.1",
297
+ "@suss/behavioral-ir": "0.37.1",
298
+ "@suss/contract-graphql": "0.37.1",
299
+ "@suss/contract-serverless": "0.37.1",
300
+ "@suss/extractor": "0.37.1",
301
+ "@suss/manifest-aws": "0.37.1",
302
+ "@suss/recognize": "0.37.1",
303
+ "@suss/sql": "0.37.1",
288
304
  "zod": "^4.0.0"
289
305
  },
290
306
  "devDependencies": {
@@ -309,6 +325,8 @@
309
325
  "@suss/framework-bigquery": "*",
310
326
  "@suss/framework-bigquery-python": "*",
311
327
  "@suss/framework-bigquery-ruby": "*",
328
+ "@suss/framework-bullmq": "*",
329
+ "@suss/framework-celery": "*",
312
330
  "@suss/framework-cloudflare-workers": "*",
313
331
  "@suss/framework-drizzle": "*",
314
332
  "@suss/framework-express": "*",
@@ -327,7 +345,9 @@
327
345
  "@suss/framework-pg": "*",
328
346
  "@suss/framework-pg-ruby": "*",
329
347
  "@suss/framework-rspec": "*",
348
+ "@suss/framework-activejob": "*",
330
349
  "@suss/framework-factory-bot": "*",
350
+ "@suss/framework-sidekiq": "*",
331
351
  "@suss/framework-fabrication": "*",
332
352
  "@suss/framework-minitest": "*",
333
353
  "@suss/framework-prisma": "*",