@ts-cloud/core 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45517,15 +45517,21 @@ function composeServerlessAppTemplate(opts) {
45517
45517
  TSCLOUD_LAMBDA_MODE: mode,
45518
45518
  TSCLOUD_ENV: environment,
45519
45519
  ...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
45520
+ ...app.scheduler === "sub-minute" ? { TSCLOUD_SCHEDULER: "sub-minute" } : {},
45520
45521
  ...cacheEnabled ? { TSCLOUD_CACHE_TABLE: `${slug}-${environment}-cache` } : {},
45521
45522
  ...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
45522
45523
  ...app.env ?? {}
45523
45524
  });
45525
+ const efsEnabled = Boolean(app.efs);
45526
+ const efsOpts = typeof app.efs === "object" ? app.efs : {};
45527
+ const efsMountPath = efsOpts.mountPath ?? "/mnt/local";
45528
+ const efsProvision = efsEnabled && !efsOpts.accessPointArn;
45529
+ const efsAccessPoint = efsOpts.accessPointArn ?? (efsProvision ? Fn2.getAtt("EfsAccessPoint", "Arn") : undefined);
45524
45530
  const subnets = app.vpc?.subnets ?? [];
45525
45531
  const hasVpc = subnets.length > 0;
45526
- const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
45532
+ const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy) || efsEnabled;
45527
45533
  if (needsDataVpc && !hasVpc) {
45528
- throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
45534
+ throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy / efs require app.vpc.subnets (private subnets) to be set.");
45529
45535
  }
45530
45536
  const vpcConfig = hasVpc ? {
45531
45537
  VpcConfig: {
@@ -45536,7 +45542,16 @@ function composeServerlessAppTemplate(opts) {
45536
45542
  ]
45537
45543
  }
45538
45544
  } : {};
45539
- function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency) {
45545
+ const efsDependsOn = efsProvision ? subnets.map((_, i) => `EfsMountTarget${i}`) : [];
45546
+ const efsConfig = efsEnabled ? { FileSystemConfigs: [{ Arn: efsAccessPoint, LocalMountPath: efsMountPath }] } : {};
45547
+ if (efsEnabled) {
45548
+ inlinePolicies[0].PolicyDocument.Statement.push({
45549
+ Effect: "Allow",
45550
+ Action: ["elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite", "elasticfilesystem:ClientRootAccess", "elasticfilesystem:DescribeMountTargets"],
45551
+ Resource: "*"
45552
+ });
45553
+ }
45554
+ function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
45540
45555
  resources[`${logicalId}LogGroup`] = {
45541
45556
  Type: "AWS::Logs::LogGroup",
45542
45557
  Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
@@ -45553,7 +45568,7 @@ function composeServerlessAppTemplate(opts) {
45553
45568
  };
45554
45569
  resources[logicalId] = {
45555
45570
  Type: "AWS::Lambda::Function",
45556
- DependsOn: [`${logicalId}LogGroup`],
45571
+ DependsOn: [`${logicalId}LogGroup`, ...efsDependsOn],
45557
45572
  Properties: {
45558
45573
  FunctionName: name,
45559
45574
  Architectures: [architecture],
@@ -45561,17 +45576,18 @@ function composeServerlessAppTemplate(opts) {
45561
45576
  Timeout: timeout,
45562
45577
  Role: Fn2.getAtt("AppRole", "Arn"),
45563
45578
  Environment: { Variables: baseEnv(mode) },
45564
- EphemeralStorage: { Size: tmpStorage },
45579
+ EphemeralStorage: { Size: tmp },
45565
45580
  ...codeProps,
45566
45581
  ...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
45567
- ...vpcConfig
45582
+ ...vpcConfig,
45583
+ ...efsConfig
45568
45584
  }
45569
45585
  };
45570
45586
  }
45571
- addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency);
45572
- addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900);
45587
+ addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency, tmpStorage);
45588
+ addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900, undefined, app.cliTmpStorage ?? tmpStorage);
45573
45589
  if (hasQueue)
45574
- addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120);
45590
+ addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120, undefined, app.queueTmpStorage ?? tmpStorage);
45575
45591
  resources.HttpApi = {
45576
45592
  Type: "AWS::ApiGatewayV2::Api",
45577
45593
  Properties: {
@@ -45618,6 +45634,59 @@ function composeServerlessAppTemplate(opts) {
45618
45634
  Value: Fn2.getAtt("HttpApi", "ApiEndpoint")
45619
45635
  };
45620
45636
  outputs.HttpApiId = { Description: "HTTP API id", Value: Fn2.ref("HttpApi") };
45637
+ const domains = (Array.isArray(app.domain) ? app.domain : app.domain ? [app.domain] : []).filter(Boolean);
45638
+ if (domains.length) {
45639
+ if (!app.certificateArn && !app.hostedZoneId) {
45640
+ throw new Error("serverless app: a custom `domain` needs either `certificateArn` (pre-issued, regional) or `hostedZoneId` (to auto-issue + validate an ACM cert).");
45641
+ }
45642
+ let certRef = app.certificateArn;
45643
+ if (!certRef) {
45644
+ resources.HttpCertificate = {
45645
+ Type: "AWS::CertificateManager::Certificate",
45646
+ Properties: {
45647
+ DomainName: domains[0],
45648
+ ...domains.length > 1 ? { SubjectAlternativeNames: domains.slice(1) } : {},
45649
+ ValidationMethod: "DNS",
45650
+ DomainValidationOptions: domains.map((d) => ({ DomainName: d, HostedZoneId: app.hostedZoneId }))
45651
+ }
45652
+ };
45653
+ certRef = Fn2.ref("HttpCertificate");
45654
+ }
45655
+ domains.forEach((d, i) => {
45656
+ const dn = `HttpDomain${i}`;
45657
+ resources[dn] = {
45658
+ Type: "AWS::ApiGatewayV2::DomainName",
45659
+ Properties: {
45660
+ DomainName: d,
45661
+ DomainNameConfigurations: [{ CertificateArn: certRef, EndpointType: "REGIONAL" }]
45662
+ }
45663
+ };
45664
+ resources[`HttpApiMapping${i}`] = {
45665
+ Type: "AWS::ApiGatewayV2::ApiMapping",
45666
+ DependsOn: ["HttpStage"],
45667
+ Properties: { ApiId: Fn2.ref("HttpApi"), DomainName: Fn2.ref(dn), Stage: "$default" }
45668
+ };
45669
+ if (app.hostedZoneId) {
45670
+ resources[`HttpDomainRecord${i}`] = {
45671
+ Type: "AWS::Route53::RecordSet",
45672
+ Properties: {
45673
+ HostedZoneId: app.hostedZoneId,
45674
+ Name: d,
45675
+ Type: "A",
45676
+ AliasTarget: {
45677
+ DNSName: Fn2.getAtt(dn, "RegionalDomainName"),
45678
+ HostedZoneId: Fn2.getAtt(dn, "RegionalHostedZoneId")
45679
+ }
45680
+ }
45681
+ };
45682
+ }
45683
+ outputs[`CustomDomain${i}`] = { Description: `Custom domain ${d}`, Value: d };
45684
+ outputs[`CustomDomainTarget${i}`] = {
45685
+ Description: `Point ${d} (CNAME/alias) at this APIGW regional domain`,
45686
+ Value: Fn2.getAtt(dn, "RegionalDomainName")
45687
+ };
45688
+ });
45689
+ }
45621
45690
  if (hasQueue) {
45622
45691
  resources.AppQueueDlq = {
45623
45692
  Type: "AWS::SQS::Queue",
@@ -45760,10 +45829,37 @@ function composeServerlessAppTemplate(opts) {
45760
45829
  DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
45761
45830
  OriginAccessControlId: Fn2.ref("AssetsOAC"),
45762
45831
  S3OriginConfig: { OriginAccessIdentity: "" }
45763
- }]
45764
- }
45832
+ }],
45833
+ ...app.assetDomain ? {
45834
+ Aliases: [app.assetDomain],
45835
+ ViewerCertificate: {
45836
+ AcmCertificateArn: app.assetCertificateArn,
45837
+ SslSupportMethod: "sni-only",
45838
+ MinimumProtocolVersion: "TLSv1.2_2021"
45839
+ }
45840
+ } : {}
45841
+ }
45842
+ }
45843
+ };
45844
+ if (app.assetDomain) {
45845
+ if (!app.assetCertificateArn)
45846
+ throw new Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");
45847
+ if (app.hostedZoneId) {
45848
+ resources.AssetsDomainRecord = {
45849
+ Type: "AWS::Route53::RecordSet",
45850
+ Properties: {
45851
+ HostedZoneId: app.hostedZoneId,
45852
+ Name: app.assetDomain,
45853
+ Type: "A",
45854
+ AliasTarget: {
45855
+ DNSName: Fn2.getAtt("AssetsDistribution", "DomainName"),
45856
+ HostedZoneId: "Z2FDTNDATAQYW2"
45857
+ }
45858
+ }
45859
+ };
45765
45860
  }
45766
- };
45861
+ outputs.AssetDomain = { Description: "Custom asset CDN host", Value: app.assetDomain };
45862
+ }
45767
45863
  resources.AssetsBucketPolicy = {
45768
45864
  Type: "AWS::S3::BucketPolicy",
45769
45865
  Properties: {
@@ -45842,6 +45938,37 @@ function composeServerlessAppTemplate(opts) {
45842
45938
  }
45843
45939
  };
45844
45940
  }
45941
+ if (efsProvision) {
45942
+ resources.EfsFileSystem = {
45943
+ Type: "AWS::EFS::FileSystem",
45944
+ Properties: {
45945
+ Encrypted: true,
45946
+ FileSystemTags: [{ Key: "Name", Value: `${slug}-${environment}-efs` }]
45947
+ }
45948
+ };
45949
+ subnets.forEach((subnetId, i) => {
45950
+ resources[`EfsMountTarget${i}`] = {
45951
+ Type: "AWS::EFS::MountTarget",
45952
+ Properties: {
45953
+ FileSystemId: Fn2.ref("EfsFileSystem"),
45954
+ SubnetId: subnetId,
45955
+ SecurityGroups: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
45956
+ }
45957
+ };
45958
+ });
45959
+ resources.EfsAccessPoint = {
45960
+ Type: "AWS::EFS::AccessPoint",
45961
+ Properties: {
45962
+ FileSystemId: Fn2.ref("EfsFileSystem"),
45963
+ PosixUser: { Uid: 1001, Gid: 1001 },
45964
+ RootDirectory: {
45965
+ Path: "/lambda",
45966
+ CreationInfo: { OwnerUid: 1001, OwnerGid: 1001, Permissions: "0755" }
45967
+ }
45968
+ }
45969
+ };
45970
+ outputs.EfsFileSystemId = { Description: "EFS file system id", Value: Fn2.ref("EfsFileSystem") };
45971
+ }
45845
45972
  if (app.cache?.driver === "elasticache") {
45846
45973
  resources.CacheSubnetGroup = {
45847
45974
  Type: "AWS::ElastiCache::SubnetGroup",
package/dist/types.d.ts CHANGED
@@ -918,9 +918,10 @@ export interface SiteConfig {
918
918
  queues?: QueueWorkerConfig[];
919
919
  /**
920
920
  * Run the Laravel scheduler for this site
921
- * (`* * * * * php artisan schedule:run`).
921
+ * (`* * * * * php artisan schedule:run`). `true` enables it with defaults;
922
+ * pass a {@link SchedulerConfig} to attach heartbeat monitoring.
922
923
  */
923
- scheduler?: boolean;
924
+ scheduler?: boolean | SchedulerConfig;
924
925
  /** Arbitrary long-running processes to keep alive (systemd-managed). */
925
926
  daemons?: DaemonConfig[];
926
927
  /** TLS configuration for this site's nginx vhost. */
@@ -949,6 +950,46 @@ export interface SiteConfig {
949
950
  * process.env.UI_PASSWORD }`. The htpasswd file is generated on the box.
950
951
  */
951
952
  auth?: SiteAuthConfig;
953
+ /**
954
+ * Package-registry credentials written into the release before
955
+ * `composer install` / `npm install` so private packages resolve (Forge's
956
+ * Composer/npm credentials feature).
957
+ */
958
+ credentials?: SiteCredentialsConfig;
959
+ /**
960
+ * Custom nginx for this site's vhost (Forge's "Edit Nginx Configuration").
961
+ * Directives are injected into the generated `server { … }` block, so the
962
+ * managed root/locations/SSL still apply.
963
+ */
964
+ nginx?: SiteNginxConfig;
965
+ }
966
+ /** Per-site nginx customization injected into the generated server block. */
967
+ export interface SiteNginxConfig {
968
+ /**
969
+ * Name of a reusable template defined in
970
+ * {@link ComputeConfig.nginxTemplates}. Its directive lines are injected into
971
+ * this site's server block.
972
+ */
973
+ template?: string;
974
+ /**
975
+ * Raw nginx directive lines added to this site's server block (after the
976
+ * managed directives), e.g. `['gzip on;', 'location /metrics { deny all; }']`.
977
+ * Applied on top of {@link template} when both are set.
978
+ */
979
+ serverSnippet?: string[];
980
+ /** `client_max_body_size` for this vhost (e.g. `'256M'`) for large uploads. */
981
+ clientMaxBodySize?: string;
982
+ }
983
+ /** Private package-registry credentials for a site's build. */
984
+ export interface SiteCredentialsConfig {
985
+ /**
986
+ * Composer `auth.json` contents — an object (serialized to JSON) or a
987
+ * ready-made JSON string. Written to the release root before `composer
988
+ * install` (e.g. `{ 'github-oauth': { 'github.com': '<token>' } }`).
989
+ */
990
+ composerAuth?: Record<string, unknown> | string;
991
+ /** `.npmrc` contents written to the release root before `npm install`. */
992
+ npmrc?: string;
952
993
  }
953
994
  /** HTTP Basic auth for a site's nginx vhost. See {@link SiteConfig.auth}. */
954
995
  export interface SiteAuthConfig {
@@ -1006,6 +1047,48 @@ export interface SiteSslConfig {
1006
1047
  certPath?: string;
1007
1048
  /** Path to the private key (PEM) when `provider: 'custom'`. */
1008
1049
  keyPath?: string;
1050
+ /**
1051
+ * Issue a **wildcard** certificate (`*.<domain>` + `<domain>`). Requires
1052
+ * DNS-01 validation, so {@link dns} must be set. The nginx plugin can't do
1053
+ * wildcards (that needs HTTP-01 per host).
1054
+ */
1055
+ wildcard?: boolean;
1056
+ /**
1057
+ * Use DNS-01 validation via a certbot DNS plugin instead of the nginx
1058
+ * (HTTP-01) challenge. Needed for wildcard certs and for issuing before the
1059
+ * domain resolves to the box. The plugin + credentials are wired on the box.
1060
+ */
1061
+ dns?: SslDnsConfig;
1062
+ }
1063
+ /** certbot DNS-01 plugin configuration for DNS-validated / wildcard certs. */
1064
+ export interface SslDnsConfig {
1065
+ /** DNS provider whose certbot plugin handles the `_acme-challenge` records. */
1066
+ provider: 'cloudflare' | 'route53' | 'digitalocean' | 'google';
1067
+ /**
1068
+ * Provider credentials written to a root-only INI certbot reads
1069
+ * (`--dns-<provider>-credentials`). For route53, AWS env/instance-role creds
1070
+ * are used instead, so this may be omitted. Keys are provider-specific, e.g.
1071
+ * `{ dns_cloudflare_api_token: '…' }`.
1072
+ */
1073
+ credentials?: Record<string, string>;
1074
+ /** Seconds to wait for DNS propagation before certbot asks the CA to verify. */
1075
+ propagationSeconds?: number;
1076
+ }
1077
+ /**
1078
+ * Laravel scheduler options for a site (Forge's scheduler + heartbeat
1079
+ * monitoring). The scheduler runs `php artisan schedule:run` every minute.
1080
+ */
1081
+ export interface SchedulerConfig {
1082
+ /**
1083
+ * Heartbeat monitor URL pinged after each successful `schedule:run`
1084
+ * (healthchecks.io, Oh Dear, Better Uptime, …). If the scheduler stops, the
1085
+ * monitor stops receiving pings and alerts you.
1086
+ */
1087
+ heartbeatUrl?: string;
1088
+ /**
1089
+ * HTTP method for the heartbeat ping. @default 'GET'
1090
+ */
1091
+ heartbeatMethod?: 'GET' | 'POST' | 'HEAD';
1009
1092
  }
1010
1093
  /**
1011
1094
  * A Laravel queue worker (or Horizon supervisor) run as a systemd service.
@@ -1123,6 +1206,34 @@ export interface DatabaseConfig {
1123
1206
  host?: string;
1124
1207
  /** Port (defaults: mysql/mariadb 3306, postgres 5432). */
1125
1208
  port?: number;
1209
+ /**
1210
+ * Additional database users to create beyond the app {@link username}
1211
+ * (the Forge Database Users feature). Each can be granted full or read-only
1212
+ * access to one or more databases. Created at provision time on the on-box
1213
+ * engine.
1214
+ */
1215
+ users?: DatabaseUserConfig[];
1216
+ }
1217
+ /**
1218
+ * An extra database user provisioned on the on-box engine (per-user grants).
1219
+ * Beyond the application user, you can create reporting/read-only accounts or
1220
+ * service-specific logins with their own access scope.
1221
+ */
1222
+ export interface DatabaseUserConfig {
1223
+ /** User name to create. */
1224
+ username: string;
1225
+ /** Password for the user. */
1226
+ password: string;
1227
+ /**
1228
+ * Databases this user may access. Defaults to the app database
1229
+ * ({@link DatabaseConfig.name}) when omitted.
1230
+ */
1231
+ databases?: string[];
1232
+ /**
1233
+ * Access level granted on {@link databases}. `all` (default) is full
1234
+ * read/write; `readonly` grants SELECT only (plus connect on Postgres).
1235
+ */
1236
+ access?: 'all' | 'readonly';
1126
1237
  }
1127
1238
  export interface CacheConfig {
1128
1239
  type?: 'redis' | 'memcached';
@@ -1530,8 +1641,12 @@ export interface ServerlessAppConfig {
1530
1641
  rdsProxy?: boolean | {
1531
1642
  name?: string;
1532
1643
  };
1533
- /** Ephemeral `/tmp` size in MB (51210240). @default 512 */
1644
+ /** Ephemeral storage in MB (512 to 10240) for the HTTP function. @default 512 */
1534
1645
  tmpStorage?: number;
1646
+ /** Ephemeral storage in MB for the CLI function. @default tmpStorage */
1647
+ cliTmpStorage?: number;
1648
+ /** Ephemeral storage in MB for the queue function. @default tmpStorage */
1649
+ queueTmpStorage?: number;
1535
1650
  /** Database attachment. */
1536
1651
  database?: {
1537
1652
  connection?: 'rds-proxy' | 'aurora-serverless' | 'rds';
@@ -1546,14 +1661,46 @@ export interface ServerlessAppConfig {
1546
1661
  storage?: {
1547
1662
  bucket?: string;
1548
1663
  };
1664
+ /**
1665
+ * Mount a shared Elastic File System on the functions (Vapor's `/mnt/local`).
1666
+ * Requires a VPC. `true` provisions an EFS file system + access point;
1667
+ * otherwise attach an existing access point by ARN. The mount path defaults
1668
+ * to `/mnt/local`.
1669
+ */
1670
+ efs?: boolean | {
1671
+ /** Existing EFS Access Point ARN to attach (skips provisioning). */
1672
+ accessPointArn?: string;
1673
+ /** Mount path inside the functions. @default '/mnt/local' */
1674
+ mountPath?: string;
1675
+ };
1549
1676
  /** Managed WAF in front of the HTTP API / CloudFront. */
1550
1677
  firewall?: WafConfig;
1551
- /** Custom domain for the app (overrides {@link EnvironmentConfig.domain}). */
1678
+ /** Custom domain(s) for the app's HTTP API (overrides {@link EnvironmentConfig.domain}). */
1552
1679
  domain?: string | string[];
1553
- /** Pre-issued ACM certificate ARN for the custom domain. */
1680
+ /** Pre-issued ACM certificate ARN for the custom domain (regional, same region). */
1554
1681
  certificateArn?: string;
1682
+ /**
1683
+ * Route53 hosted zone ID for the custom domain. When set (and no
1684
+ * `certificateArn`), ts-cloud issues + DNS-validates an ACM cert and creates
1685
+ * the alias record automatically. Without it, supply `certificateArn` and
1686
+ * point your DNS at the API's regional domain (emitted as a stack output).
1687
+ */
1688
+ hostedZoneId?: string;
1555
1689
  /** Local directory whose contents are uploaded to S3/CloudFront as versioned assets. */
1556
1690
  assets?: string;
1691
+ /**
1692
+ * Serve assets from a custom CDN host instead of the default CloudFront domain
1693
+ * (Vapor `asset-domain`). Requires a us-east-1 ACM cert via assetCertificateArn.
1694
+ */
1695
+ assetDomain?: string;
1696
+ /** us-east-1 ACM certificate ARN for {@link assetDomain} (CloudFront requirement). */
1697
+ assetCertificateArn?: string;
1698
+ /** Include dotfiles when uploading assets (Vapor `dot-files-as-assets`). @default false */
1699
+ dotFilesAsAssets?: boolean;
1700
+ /** Serve assets from the app/root domain too (Vapor `serve_assets`). Injected as env. */
1701
+ serveAssets?: boolean;
1702
+ /** Redirect robots.txt to the asset CDN (Vapor `redirect_robots_txt`). Injected as env. @default true */
1703
+ redirectRobotsTxt?: boolean;
1557
1704
  /** PHP version for the runtime layer. @default '8.3' */
1558
1705
  phpVersion?: PhpVersion;
1559
1706
  /** CPU architecture. @default 'x86_64' */
@@ -2059,6 +2206,13 @@ export interface ComputeConfig {
2059
2206
  * @default 'nginx'
2060
2207
  */
2061
2208
  webServer?: 'nginx' | 'rpx';
2209
+ /**
2210
+ * Reusable nginx config templates, keyed by name. A site references one via
2211
+ * `site.nginx.template`, and its directive lines are injected into that
2212
+ * site's server block — define a hardening/caching/proxy snippet once and
2213
+ * share it across sites (Forge's nginx templates).
2214
+ */
2215
+ nginxTemplates?: Record<string, string[]>;
2062
2216
  /**
2063
2217
  * On-box managed services to install (Forge's single-server model): the
2064
2218
  * database engine, cache, and search. Each may be `true` for defaults or an
@@ -2146,6 +2300,19 @@ export interface ComputePhpConfig {
2146
2300
  * e.g. `['imagick', 'swoole']`.
2147
2301
  */
2148
2302
  extensions?: string[];
2303
+ /**
2304
+ * Apply the production OPcache + php.ini tuning (Forge's "Optimize for
2305
+ * Production"): OPcache on with timestamp validation off, larger file/string
2306
+ * buffers, and a bigger realpath cache. Deploys restart php-fpm, so disabled
2307
+ * timestamp validation is safe. @default true
2308
+ */
2309
+ optimizeForProduction?: boolean;
2310
+ /**
2311
+ * Extra `php.ini` directives merged on top of the production tuning (and
2312
+ * applied even when {@link optimizeForProduction} is false), e.g.
2313
+ * `{ memory_limit: '512M', upload_max_filesize: '128M' }`.
2314
+ */
2315
+ ini?: Record<string, string>;
2149
2316
  }
2150
2317
  /**
2151
2318
  * On-box managed services (database / cache / search) for a compute box.
@@ -3191,7 +3358,7 @@ export interface RealtimeHooksConfig {
3191
3358
  * })
3192
3359
  *
3193
3360
  * // Private channel
3194
- * Echo.private(`user.${userId}`).listen('notification', (e) => {
3361
+ * Echo.private('user.' + userId).listen('notification', (e) => {
3195
3362
  * console.log('Private notification:', e)
3196
3363
  * })
3197
3364
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "description": "Core CloudFormation generation library for ts-cloud",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ts-cloud/aws-types": "0.5.0"
34
+ "@ts-cloud/aws-types": "0.5.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3"