@stackframe/stack-shared 2.8.25 → 2.8.27

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/config/format.d.mts +20 -5
  3. package/dist/config/format.d.ts +20 -5
  4. package/dist/config/format.js +39 -14
  5. package/dist/config/format.js.map +1 -1
  6. package/dist/config/schema.d.mts +795 -317
  7. package/dist/config/schema.d.ts +795 -317
  8. package/dist/config/schema.js +471 -84
  9. package/dist/config/schema.js.map +1 -1
  10. package/dist/crud.d.mts +1 -0
  11. package/dist/crud.d.ts +1 -0
  12. package/dist/esm/config/format.js +37 -14
  13. package/dist/esm/config/format.js.map +1 -1
  14. package/dist/esm/config/schema.js +460 -80
  15. package/dist/esm/config/schema.js.map +1 -1
  16. package/dist/esm/helpers/emails.js +136 -30
  17. package/dist/esm/helpers/emails.js.map +1 -1
  18. package/dist/esm/interface/admin-interface.js +19 -29
  19. package/dist/esm/interface/admin-interface.js.map +1 -1
  20. package/dist/esm/known-errors.js +24 -1
  21. package/dist/esm/known-errors.js.map +1 -1
  22. package/dist/esm/schema-fields.js +92 -27
  23. package/dist/esm/schema-fields.js.map +1 -1
  24. package/dist/esm/utils/currencies.js +52 -0
  25. package/dist/esm/utils/currencies.js.map +1 -0
  26. package/dist/esm/utils/dates.js +55 -1
  27. package/dist/esm/utils/dates.js.map +1 -1
  28. package/dist/esm/utils/errors.js.map +1 -1
  29. package/dist/esm/utils/objects.js +2 -0
  30. package/dist/esm/utils/objects.js.map +1 -1
  31. package/dist/esm/utils/strings.js +4 -0
  32. package/dist/esm/utils/strings.js.map +1 -1
  33. package/dist/esm/utils/types.js +45 -0
  34. package/dist/esm/utils/types.js.map +1 -1
  35. package/dist/helpers/emails.d.mts +32 -6
  36. package/dist/helpers/emails.d.ts +32 -6
  37. package/dist/helpers/emails.js +138 -30
  38. package/dist/helpers/emails.js.map +1 -1
  39. package/dist/helpers/password.d.mts +1 -0
  40. package/dist/helpers/password.d.ts +1 -0
  41. package/dist/helpers/production-mode.d.mts +1 -0
  42. package/dist/helpers/production-mode.d.ts +1 -0
  43. package/dist/index.d.mts +2 -2
  44. package/dist/index.d.ts +2 -2
  45. package/dist/interface/admin-interface.d.mts +8 -8
  46. package/dist/interface/admin-interface.d.ts +8 -8
  47. package/dist/interface/admin-interface.js +19 -29
  48. package/dist/interface/admin-interface.js.map +1 -1
  49. package/dist/interface/client-interface.d.mts +1 -0
  50. package/dist/interface/client-interface.d.ts +1 -0
  51. package/dist/interface/crud/connected-accounts.d.mts +1 -0
  52. package/dist/interface/crud/connected-accounts.d.ts +1 -0
  53. package/dist/interface/crud/contact-channels.d.mts +1 -0
  54. package/dist/interface/crud/contact-channels.d.ts +1 -0
  55. package/dist/interface/crud/current-user.d.mts +1 -0
  56. package/dist/interface/crud/current-user.d.ts +1 -0
  57. package/dist/interface/crud/email-templates.d.mts +1 -0
  58. package/dist/interface/crud/email-templates.d.ts +1 -0
  59. package/dist/interface/crud/emails.d.mts +1 -0
  60. package/dist/interface/crud/emails.d.ts +1 -0
  61. package/dist/interface/crud/internal-api-keys.d.mts +1 -0
  62. package/dist/interface/crud/internal-api-keys.d.ts +1 -0
  63. package/dist/interface/crud/notification-preferences.d.mts +1 -0
  64. package/dist/interface/crud/notification-preferences.d.ts +1 -0
  65. package/dist/interface/crud/oauth-providers.d.mts +5 -4
  66. package/dist/interface/crud/oauth-providers.d.ts +5 -4
  67. package/dist/interface/crud/project-api-keys.d.mts +3 -2
  68. package/dist/interface/crud/project-api-keys.d.ts +3 -2
  69. package/dist/interface/crud/project-permissions.d.mts +1 -0
  70. package/dist/interface/crud/project-permissions.d.ts +1 -0
  71. package/dist/interface/crud/projects.d.mts +8 -7
  72. package/dist/interface/crud/projects.d.ts +8 -7
  73. package/dist/interface/crud/sessions.d.mts +1 -0
  74. package/dist/interface/crud/sessions.d.ts +1 -0
  75. package/dist/interface/crud/svix-token.d.mts +1 -0
  76. package/dist/interface/crud/svix-token.d.ts +1 -0
  77. package/dist/interface/crud/team-invitation-details.d.mts +1 -0
  78. package/dist/interface/crud/team-invitation-details.d.ts +1 -0
  79. package/dist/interface/crud/team-invitation.d.mts +1 -0
  80. package/dist/interface/crud/team-invitation.d.ts +1 -0
  81. package/dist/interface/crud/team-member-profiles.d.mts +1 -0
  82. package/dist/interface/crud/team-member-profiles.d.ts +1 -0
  83. package/dist/interface/crud/team-memberships.d.mts +1 -0
  84. package/dist/interface/crud/team-memberships.d.ts +1 -0
  85. package/dist/interface/crud/team-permissions.d.mts +1 -0
  86. package/dist/interface/crud/team-permissions.d.ts +1 -0
  87. package/dist/interface/crud/teams.d.mts +1 -0
  88. package/dist/interface/crud/teams.d.ts +1 -0
  89. package/dist/interface/crud/users.d.mts +1 -0
  90. package/dist/interface/crud/users.d.ts +1 -0
  91. package/dist/interface/server-interface.d.mts +1 -0
  92. package/dist/interface/server-interface.d.ts +1 -0
  93. package/dist/known-errors.d.mts +6 -0
  94. package/dist/known-errors.d.ts +6 -0
  95. package/dist/known-errors.js +24 -1
  96. package/dist/known-errors.js.map +1 -1
  97. package/dist/schema-fields.d.mts +39 -8
  98. package/dist/schema-fields.d.ts +39 -8
  99. package/dist/schema-fields.js +101 -27
  100. package/dist/schema-fields.js.map +1 -1
  101. package/dist/utils/currencies.d.mts +39 -0
  102. package/dist/utils/currencies.d.ts +39 -0
  103. package/dist/utils/currencies.js +78 -0
  104. package/dist/utils/currencies.js.map +1 -0
  105. package/dist/utils/dates.d.mts +5 -1
  106. package/dist/utils/dates.d.ts +5 -1
  107. package/dist/utils/dates.js +58 -2
  108. package/dist/utils/dates.js.map +1 -1
  109. package/dist/utils/errors.d.mts +9 -0
  110. package/dist/utils/errors.d.ts +9 -0
  111. package/dist/utils/errors.js.map +1 -1
  112. package/dist/utils/objects.d.mts +23 -8
  113. package/dist/utils/objects.d.ts +23 -8
  114. package/dist/utils/objects.js +2 -0
  115. package/dist/utils/objects.js.map +1 -1
  116. package/dist/utils/strings.d.mts +3 -1
  117. package/dist/utils/strings.d.ts +3 -1
  118. package/dist/utils/strings.js +5 -0
  119. package/dist/utils/strings.js.map +1 -1
  120. package/dist/utils/types.d.mts +73 -2
  121. package/dist/utils/types.d.ts +73 -2
  122. package/dist/utils/types.js +54 -0
  123. package/dist/utils/types.js.map +1 -1
  124. package/package.json +1 -1
@@ -1,110 +1,122 @@
1
1
  // src/config/schema.ts
2
+ import * as yup from "yup";
3
+ import { DEFAULT_EMAIL_TEMPLATES, DEFAULT_EMAIL_THEMES, DEFAULT_EMAIL_THEME_ID } from "../helpers/emails.js";
2
4
  import * as schemaFields from "../schema-fields.js";
3
- import { yupBoolean, yupObject, yupRecord, yupString, yupUnion } from "../schema-fields.js";
5
+ import { yupBoolean, yupDate, yupMixed, yupNever, yupNumber, yupObject, yupRecord, yupString, yupTuple, yupUnion } from "../schema-fields.js";
6
+ import { isShallowEqual } from "../utils/arrays.js";
7
+ import { StackAssertionError } from "../utils/errors.js";
4
8
  import { allProviders } from "../utils/oauth.js";
5
- import { get, has, isObjectLike, mapValues, set } from "../utils/objects.js";
6
- import { DEFAULT_EMAIL_THEME_ID, DEFAULT_EMAIL_THEMES, DEFAULT_EMAIL_TEMPLATES } from "../helpers/emails.js";
9
+ import { deleteKey, filterUndefined, get, has, isObjectLike, mapValues, set, typedAssign, typedFromEntries } from "../utils/objects.js";
10
+ import { Result } from "../utils/results.js";
11
+ import { typeAssert, typeAssertExtends, typeAssertIs } from "../utils/types.js";
12
+ import { NormalizationError, assertNormalized, getInvalidConfigReason, normalize } from "./format.js";
7
13
  var configLevels = ["project", "branch", "environment", "organization"];
8
14
  var permissionRegex = /^\$?[a-z0-9_:]+$/;
9
15
  var customPermissionRegex = /^[a-z0-9_:]+$/;
16
+ function canNoLongerBeOverridden(schema, keys) {
17
+ const notOmitted = schema.concat(yupObject(
18
+ Object.fromEntries(keys.map((key) => [key, schema.getNested(key).meta({ stackConfigCanNoLongerBeOverridden: true })]))
19
+ ));
20
+ return notOmitted;
21
+ }
10
22
  var projectConfigSchema = yupObject({
11
23
  sourceOfTruth: yupUnion(
12
24
  yupObject({
13
- type: yupString().oneOf(["hosted"]).optional()
14
- }).defined(),
25
+ type: yupString().oneOf(["hosted"]).defined()
26
+ }),
15
27
  yupObject({
16
- type: yupString().oneOf(["neon"]).optional(),
28
+ type: yupString().oneOf(["neon"]).defined(),
17
29
  connectionStrings: yupRecord(
18
30
  yupString().defined(),
19
31
  yupString().defined()
20
32
  ).defined()
21
- }).defined(),
33
+ }),
22
34
  yupObject({
23
- type: yupString().oneOf(["postgres"]).optional(),
35
+ type: yupString().oneOf(["postgres"]).defined(),
24
36
  connectionString: yupString().defined()
25
- }).defined()
26
- ).optional()
37
+ })
38
+ )
27
39
  });
28
40
  var branchRbacDefaultPermissions = yupRecord(
29
- yupString().optional().matches(permissionRegex),
41
+ yupString().matches(permissionRegex),
30
42
  yupBoolean().isTrue().optional()
31
- ).optional();
43
+ );
32
44
  var branchRbacSchema = yupObject({
33
45
  permissions: yupRecord(
34
- yupString().optional().matches(customPermissionRegex),
46
+ yupString().matches(customPermissionRegex),
35
47
  yupObject({
36
48
  description: yupString().optional(),
37
49
  scope: yupString().oneOf(["team", "project"]).optional(),
38
50
  containedPermissionIds: yupRecord(
39
- yupString().optional().matches(permissionRegex),
51
+ yupString().matches(permissionRegex),
40
52
  yupBoolean().isTrue().optional()
41
53
  ).optional()
42
54
  }).optional()
43
- ).optional(),
55
+ ),
44
56
  defaultPermissions: yupObject({
45
57
  teamCreator: branchRbacDefaultPermissions,
46
58
  teamMember: branchRbacDefaultPermissions,
47
59
  signUp: branchRbacDefaultPermissions
48
- }).optional()
49
- }).optional();
60
+ })
61
+ });
50
62
  var branchApiKeysSchema = yupObject({
51
63
  enabled: yupObject({
52
- team: yupBoolean().optional(),
53
- user: yupBoolean().optional()
54
- }).optional()
55
- }).optional();
64
+ team: yupBoolean(),
65
+ user: yupBoolean()
66
+ })
67
+ });
56
68
  var branchAuthSchema = yupObject({
57
- allowSignUp: yupBoolean().optional(),
69
+ allowSignUp: yupBoolean(),
58
70
  password: yupObject({
59
- allowSignIn: yupBoolean().optional()
60
- }).optional(),
71
+ allowSignIn: yupBoolean()
72
+ }),
61
73
  otp: yupObject({
62
- allowSignIn: yupBoolean().optional()
63
- }).optional(),
74
+ allowSignIn: yupBoolean()
75
+ }),
64
76
  passkey: yupObject({
65
- allowSignIn: yupBoolean().optional()
66
- }).optional(),
77
+ allowSignIn: yupBoolean()
78
+ }),
67
79
  oauth: yupObject({
68
80
  accountMergeStrategy: yupString().oneOf(["link_method", "raise_error", "allow_duplicates"]).optional(),
69
81
  providers: yupRecord(
70
- yupString().optional().matches(permissionRegex),
82
+ yupString().matches(permissionRegex),
71
83
  yupObject({
72
84
  type: yupString().oneOf(allProviders).optional(),
73
- allowSignIn: yupBoolean().optional(),
74
- allowConnectedAccounts: yupBoolean().optional()
75
- }).defined()
76
- ).optional()
77
- }).optional()
78
- }).optional();
85
+ allowSignIn: yupBoolean(),
86
+ allowConnectedAccounts: yupBoolean()
87
+ })
88
+ )
89
+ })
90
+ });
79
91
  var branchDomain = yupObject({
80
- allowLocalhost: yupBoolean().optional()
81
- }).optional();
82
- var branchConfigSchema = projectConfigSchema.omit(["sourceOfTruth"]).concat(yupObject({
92
+ allowLocalhost: yupBoolean()
93
+ });
94
+ var branchConfigSchema = canNoLongerBeOverridden(projectConfigSchema, ["sourceOfTruth"]).concat(yupObject({
83
95
  rbac: branchRbacSchema,
84
96
  teams: yupObject({
85
- createPersonalTeamOnSignUp: yupBoolean().optional(),
86
- allowClientTeamCreation: yupBoolean().optional()
87
- }).optional(),
97
+ createPersonalTeamOnSignUp: yupBoolean(),
98
+ allowClientTeamCreation: yupBoolean()
99
+ }),
88
100
  users: yupObject({
89
- allowClientUserDeletion: yupBoolean().optional()
90
- }).optional(),
101
+ allowClientUserDeletion: yupBoolean()
102
+ }),
91
103
  apiKeys: branchApiKeysSchema,
92
104
  domains: branchDomain,
93
105
  auth: branchAuthSchema,
94
106
  emails: yupObject({
95
- theme: schemaFields.emailThemeSchema.optional(),
96
- themeList: schemaFields.emailThemeListSchema.optional(),
97
- templateList: schemaFields.emailTemplateListSchema.optional()
107
+ selectedThemeId: schemaFields.emailThemeSchema,
108
+ themes: schemaFields.emailThemeListSchema,
109
+ templates: schemaFields.emailTemplateListSchema
98
110
  })
99
111
  }));
100
112
  var environmentConfigSchema = branchConfigSchema.concat(yupObject({
101
113
  auth: branchConfigSchema.getNested("auth").concat(yupObject({
102
114
  oauth: branchConfigSchema.getNested("auth").getNested("oauth").concat(yupObject({
103
115
  providers: yupRecord(
104
- yupString().optional().matches(permissionRegex),
116
+ yupString().matches(permissionRegex),
105
117
  yupObject({
106
118
  type: yupString().oneOf(allProviders).optional(),
107
- isShared: yupBoolean().optional(),
119
+ isShared: yupBoolean(),
108
120
  clientId: schemaFields.oauthClientIdSchema.optional(),
109
121
  clientSecret: schemaFields.oauthClientSecretSchema.optional(),
110
122
  facebookConfigId: schemaFields.oauthFacebookConfigIdSchema.optional(),
@@ -112,12 +124,12 @@ var environmentConfigSchema = branchConfigSchema.concat(yupObject({
112
124
  allowSignIn: yupBoolean().optional(),
113
125
  allowConnectedAccounts: yupBoolean().optional()
114
126
  })
115
- ).optional()
116
- }).optional())
127
+ )
128
+ }))
117
129
  })),
118
130
  emails: branchConfigSchema.getNested("emails").concat(yupObject({
119
131
  server: yupObject({
120
- isShared: yupBoolean().optional(),
132
+ isShared: yupBoolean(),
121
133
  host: schemaFields.emailHostSchema.optional().nonEmpty(),
122
134
  port: schemaFields.emailPortSchema.optional(),
123
135
  username: schemaFields.emailUsernameSchema.optional().nonEmpty(),
@@ -125,32 +137,103 @@ var environmentConfigSchema = branchConfigSchema.concat(yupObject({
125
137
  senderName: schemaFields.emailSenderNameSchema.optional().nonEmpty(),
126
138
  senderEmail: schemaFields.emailSenderEmailSchema.optional().nonEmpty()
127
139
  })
128
- }).optional()),
140
+ })),
129
141
  domains: branchConfigSchema.getNested("domains").concat(yupObject({
130
142
  trustedDomains: yupRecord(
131
- yupString().uuid().optional(),
143
+ yupString(),
132
144
  yupObject({
133
- baseUrl: schemaFields.urlSchema.optional(),
134
- handlerPath: schemaFields.handlerPathSchema.optional()
145
+ baseUrl: schemaFields.urlSchema,
146
+ handlerPath: schemaFields.handlerPathSchema
135
147
  })
136
- ).optional()
148
+ )
137
149
  }))
138
150
  }));
139
151
  var organizationConfigSchema = environmentConfigSchema.concat(yupObject({}));
152
+ function migrateConfigOverride(type, oldUnmigratedConfigOverride) {
153
+ const isBranchOrHigher = ["branch", "environment", "organization"].includes(type);
154
+ const isEnvironmentOrHigher = ["environment", "organization"].includes(type);
155
+ let res = oldUnmigratedConfigOverride;
156
+ if (isBranchOrHigher) {
157
+ res = renameProperty(res, "emails.theme", "emails.selectedThemeId");
158
+ }
159
+ if (isEnvironmentOrHigher) {
160
+ res = mapProperty(res, "domains.trustedDomains", (value) => {
161
+ if (Array.isArray(value)) {
162
+ return typedFromEntries(value.map((v, i) => [`${i}`, v]));
163
+ }
164
+ return value;
165
+ });
166
+ }
167
+ if (isBranchOrHigher) {
168
+ res = removeProperty(res, "emails.themeList");
169
+ res = removeProperty(res, "emails.templateList");
170
+ }
171
+ if (type === "environment") {
172
+ res = removeProperty(res, "sourceOfTruth");
173
+ }
174
+ return res;
175
+ }
176
+ function removeProperty(obj, path) {
177
+ return mapProperty(obj, path, () => void 0);
178
+ }
179
+ function mapProperty(obj, path, mapper) {
180
+ const keyParts = path.split(".");
181
+ for (let i = 0; i < keyParts.length; i++) {
182
+ const pathPrefix = keyParts.slice(0, i).join(".");
183
+ const pathSuffix = keyParts.slice(i).join(".");
184
+ if (has(obj, pathPrefix) && isObjectLike(get(obj, pathPrefix))) {
185
+ const newValue = mapProperty(get(obj, pathPrefix), pathSuffix, mapper);
186
+ set(obj, pathPrefix, newValue);
187
+ }
188
+ }
189
+ if (has(obj, path)) {
190
+ const newValue = mapper(get(obj, path));
191
+ if (newValue !== void 0) {
192
+ set(obj, path, newValue);
193
+ } else {
194
+ deleteKey(obj, path);
195
+ }
196
+ }
197
+ return obj;
198
+ }
199
+ function renameProperty(obj, oldPath, newPath) {
200
+ const oldKeyParts = oldPath.split(".");
201
+ const newKeyParts = newPath.split(".");
202
+ if (!isShallowEqual(oldKeyParts.slice(0, -1), newKeyParts.slice(0, -1))) throw new StackAssertionError(`oldPath and newPath must have the same prefix. Provided: ${oldPath} and ${newPath}`);
203
+ for (let i = 0; i < oldKeyParts.length; i++) {
204
+ const pathPrefix = oldKeyParts.slice(0, i).join(".");
205
+ const oldPathSuffix = oldKeyParts.slice(i).join(".");
206
+ const newPathSuffix = newKeyParts.slice(i).join(".");
207
+ if (has(obj, pathPrefix) && isObjectLike(get(obj, pathPrefix))) {
208
+ set(obj, pathPrefix, renameProperty(get(obj, pathPrefix), oldPathSuffix, newPathSuffix));
209
+ }
210
+ }
211
+ if (has(obj, oldPath)) {
212
+ set(obj, newPath, get(obj, oldPath));
213
+ deleteKey(obj, oldPath);
214
+ }
215
+ return obj;
216
+ }
140
217
  var projectConfigDefaults = {
141
218
  sourceOfTruth: {
142
- type: "hosted"
219
+ type: "hosted",
220
+ connectionStrings: void 0,
221
+ connectionString: void 0
143
222
  }
144
223
  };
145
224
  var branchConfigDefaults = {};
146
225
  var environmentConfigDefaults = {};
147
226
  var organizationConfigDefaults = {
148
227
  rbac: {
149
- permissions: (key) => ({}),
228
+ permissions: (key) => ({
229
+ containedPermissionIds: (key2) => void 0,
230
+ description: void 0,
231
+ scope: void 0
232
+ }),
150
233
  defaultPermissions: {
151
- teamCreator: {},
152
- teamMember: {},
153
- signUp: {}
234
+ teamCreator: (key) => void 0,
235
+ teamMember: (key) => void 0,
236
+ signUp: (key) => void 0
154
237
  }
155
238
  },
156
239
  apiKeys: {
@@ -169,6 +252,7 @@ var organizationConfigDefaults = {
169
252
  domains: {
170
253
  allowLocalhost: false,
171
254
  trustedDomains: (key) => ({
255
+ baseUrl: void 0,
172
256
  handlerPath: "/handler"
173
257
  })
174
258
  },
@@ -186,45 +270,341 @@ var organizationConfigDefaults = {
186
270
  oauth: {
187
271
  accountMergeStrategy: "link_method",
188
272
  providers: (key) => ({
273
+ type: void 0,
189
274
  isShared: true,
190
275
  allowSignIn: false,
191
- allowConnectedAccounts: false
276
+ allowConnectedAccounts: false,
277
+ clientId: void 0,
278
+ clientSecret: void 0,
279
+ facebookConfigId: void 0,
280
+ microsoftTenantId: void 0
192
281
  })
193
282
  }
194
283
  },
195
284
  emails: {
196
285
  server: {
197
- isShared: true
286
+ isShared: true,
287
+ host: void 0,
288
+ port: void 0,
289
+ username: void 0,
290
+ password: void 0,
291
+ senderName: void 0,
292
+ senderEmail: void 0
198
293
  },
199
- theme: DEFAULT_EMAIL_THEME_ID,
200
- themeList: DEFAULT_EMAIL_THEMES,
201
- templateList: DEFAULT_EMAIL_TEMPLATES
294
+ selectedThemeId: DEFAULT_EMAIL_THEME_ID,
295
+ themes: typedAssign((key) => ({
296
+ displayName: "Unnamed Theme",
297
+ tsxSource: "Error: Theme config is missing TypeScript source code."
298
+ }), DEFAULT_EMAIL_THEMES),
299
+ templates: typedAssign((key) => ({
300
+ displayName: "Unnamed Template",
301
+ tsxSource: "Error: Template config is missing TypeScript source code.",
302
+ themeId: void 0
303
+ }), DEFAULT_EMAIL_TEMPLATES)
202
304
  }
203
305
  };
306
+ typeAssertIs()();
307
+ typeAssertIs()();
308
+ function deepReplaceFunctionsWithObjects(obj) {
309
+ return mapValues({ ...obj }, (v) => isObjectLike(v) ? deepReplaceFunctionsWithObjects(v) : v);
310
+ }
204
311
  function applyDefaults(defaults, config) {
205
- const res = typeof defaults === "function" ? {} : mapValues(defaults, (v) => typeof v === "function" ? {} : typeof v === "object" ? applyDefaults(v, {}) : v);
206
- for (const [key, mergeValue] of Object.entries(config)) {
207
- const baseValue = typeof defaults === "function" ? defaults(key) : has(defaults, key) ? get(defaults, key) : void 0;
208
- if (baseValue !== void 0) {
209
- if (isObjectLike(baseValue) && isObjectLike(mergeValue)) {
210
- set(res, key, applyDefaults(baseValue, mergeValue));
211
- continue;
312
+ const res = deepReplaceFunctionsWithObjects(defaults);
313
+ outer: for (const [key, mergeValue] of Object.entries(config)) {
314
+ if (mergeValue === void 0) continue;
315
+ const keyParts = key.split(".");
316
+ let baseValue = defaults;
317
+ let currentRes = res;
318
+ for (const [index, part] of keyParts.entries()) {
319
+ baseValue = has(baseValue, part) ? get(baseValue, part) : typeof baseValue === "function" ? baseValue(part) : void 0;
320
+ if (baseValue === void 0 || !isObjectLike(baseValue)) {
321
+ set(res, key, mergeValue);
322
+ continue outer;
212
323
  }
324
+ if (!has(currentRes, part)) set(currentRes, part, deepReplaceFunctionsWithObjects(baseValue));
325
+ currentRes = get(currentRes, part);
213
326
  }
214
- set(res, key, mergeValue);
327
+ set(res, key, isObjectLike(mergeValue) ? applyDefaults(baseValue, mergeValue) : mergeValue);
215
328
  }
216
329
  return res;
217
330
  }
331
+ function applyProjectDefaults(config) {
332
+ return applyDefaults(projectConfigDefaults, config);
333
+ }
334
+ function applyBranchDefaults(config) {
335
+ return applyDefaults(
336
+ branchConfigDefaults,
337
+ applyDefaults(
338
+ projectConfigDefaults,
339
+ config
340
+ )
341
+ );
342
+ }
343
+ function applyEnvironmentDefaults(config) {
344
+ return applyDefaults(
345
+ environmentConfigDefaults,
346
+ applyDefaults(
347
+ branchConfigDefaults,
348
+ applyDefaults(
349
+ projectConfigDefaults,
350
+ config
351
+ )
352
+ )
353
+ );
354
+ }
355
+ function applyOrganizationDefaults(config) {
356
+ return applyDefaults(
357
+ organizationConfigDefaults,
358
+ applyDefaults(
359
+ environmentConfigDefaults,
360
+ applyDefaults(
361
+ branchConfigDefaults,
362
+ applyDefaults(
363
+ projectConfigDefaults,
364
+ config
365
+ )
366
+ )
367
+ )
368
+ );
369
+ }
370
+ async function sanitizeProjectConfig(config) {
371
+ assertNormalized(config);
372
+ const oldSourceOfTruth = config.sourceOfTruth;
373
+ const sourceOfTruth = oldSourceOfTruth.type === "neon" && typeof oldSourceOfTruth.connectionStrings === "object" ? {
374
+ type: "neon",
375
+ connectionStrings: { ...filterUndefined(oldSourceOfTruth.connectionStrings) }
376
+ } : oldSourceOfTruth.type === "postgres" && typeof oldSourceOfTruth.connectionString === "string" ? {
377
+ type: "postgres",
378
+ connectionString: oldSourceOfTruth.connectionString
379
+ } : {
380
+ type: "hosted"
381
+ };
382
+ return {
383
+ ...config,
384
+ sourceOfTruth
385
+ };
386
+ }
387
+ async function sanitizeBranchConfig(config) {
388
+ assertNormalized(config);
389
+ const prepared = await sanitizeProjectConfig(config);
390
+ return {
391
+ ...prepared
392
+ };
393
+ }
394
+ async function sanitizeEnvironmentConfig(config) {
395
+ assertNormalized(config);
396
+ const prepared = await sanitizeBranchConfig(config);
397
+ return {
398
+ ...prepared
399
+ };
400
+ }
401
+ async function sanitizeOrganizationConfig(config) {
402
+ assertNormalized(config);
403
+ const prepared = await sanitizeEnvironmentConfig(config);
404
+ const themes = {
405
+ ...DEFAULT_EMAIL_THEMES,
406
+ ...prepared.emails.themes
407
+ };
408
+ const templates = {
409
+ ...DEFAULT_EMAIL_TEMPLATES,
410
+ ...prepared.emails.templates
411
+ };
412
+ return {
413
+ ...prepared,
414
+ emails: {
415
+ ...prepared.emails,
416
+ selectedThemeId: has(themes, prepared.emails.selectedThemeId) ? prepared.emails.selectedThemeId : DEFAULT_EMAIL_THEME_ID,
417
+ themes,
418
+ templates
419
+ }
420
+ };
421
+ }
422
+ async function getConfigOverrideErrors(schema, configOverride, options = {}) {
423
+ if (typeof configOverride !== "object" || configOverride === null) {
424
+ return Result.error("Config override must be a non-null object.");
425
+ }
426
+ if (Object.getPrototypeOf(configOverride) !== Object.getPrototypeOf({})) {
427
+ return Result.error("Config override must be plain old JavaScript object.");
428
+ }
429
+ const reason = getInvalidConfigReason(configOverride, { configName: "override" });
430
+ if (reason) return Result.error("Invalid config format: " + reason);
431
+ const getSubSchema = (schema2, key) => {
432
+ const keyParts = key.split(".");
433
+ if (!schema2.hasNested(keyParts[0])) {
434
+ return void 0;
435
+ }
436
+ const nestedSchema = schema2.getNested(keyParts[0]);
437
+ if (nestedSchema.meta()?.stackConfigCanNoLongerBeOverridden && !options.allowPropertiesThatCanNoLongerBeOverridden) {
438
+ return void 0;
439
+ }
440
+ if (keyParts.length === 1) {
441
+ return nestedSchema;
442
+ } else {
443
+ return getSubSchema(nestedSchema, keyParts.slice(1).join("."));
444
+ }
445
+ };
446
+ const getRestrictedSchemaBase = (path, schema2) => {
447
+ const schemaInfo = schema2.meta()?.stackSchemaInfo;
448
+ switch (schemaInfo?.type) {
449
+ case "string": {
450
+ const stringSchema = schema2;
451
+ const description = stringSchema.describe();
452
+ let res = yupString();
453
+ if (description.tests.some((t) => t.name === "uuid")) {
454
+ res = res.uuid();
455
+ }
456
+ return res;
457
+ }
458
+ case "number": {
459
+ return yupNumber();
460
+ }
461
+ case "boolean": {
462
+ return yupBoolean();
463
+ }
464
+ case "date": {
465
+ return yupDate();
466
+ }
467
+ case "mixed": {
468
+ return yupMixed();
469
+ }
470
+ case "array": {
471
+ throw new StackAssertionError(`Arrays are not supported in config JSON files (besides tuples). Use a record instead.`, { schemaInfo, schema: schema2 });
472
+ }
473
+ case "tuple": {
474
+ return yupTuple(schemaInfo.items.map((s, index) => getRestrictedSchema(path + `[${index}]`, s)));
475
+ }
476
+ case "union": {
477
+ const schemas = schemaInfo.items;
478
+ const nonObjectSchemas = [...schemas.entries()].filter(([index, s]) => s.meta()?.stackSchemaInfo?.type !== "object");
479
+ const objectSchemas = schemas.filter((s) => s.meta()?.stackSchemaInfo?.type === "object");
480
+ const allObjectSchemaKeys = [...new Set(objectSchemas.flatMap((s) => Object.keys(s.fields)))];
481
+ const mergedObjectSchema = yupObject(
482
+ Object.fromEntries(
483
+ allObjectSchemaKeys.map((key) => [key, yupUnion(
484
+ ...objectSchemas.flatMap((s, index) => s.hasNested(key) ? [s.getNested(key)] : [])
485
+ )])
486
+ )
487
+ );
488
+ return yupUnion(
489
+ ...nonObjectSchemas.map(([index, s]) => getRestrictedSchema(path + `|variant-${index}|`, s)),
490
+ ...objectSchemas.length > 0 ? [getRestrictedSchema(path + (nonObjectSchemas.length > 0 ? `|variant|` : ""), mergedObjectSchema)] : []
491
+ );
492
+ }
493
+ case "record": {
494
+ return yupRecord(getRestrictedSchema(path + ".key", schemaInfo.keySchema), getRestrictedSchema(path + ".value", schemaInfo.valueSchema));
495
+ }
496
+ case "object": {
497
+ const objectSchema = schema2;
498
+ return yupObject(
499
+ Object.fromEntries(
500
+ Object.entries(objectSchema.fields).map(([key, value]) => [key, getRestrictedSchema(path + "." + key, value)])
501
+ )
502
+ );
503
+ }
504
+ case "never": {
505
+ return yupNever();
506
+ }
507
+ default: {
508
+ throw new StackAssertionError(`Unknown schema info at path ${path}: ${JSON.stringify(schemaInfo)}`, { schemaInfo, schema: schema2 });
509
+ }
510
+ }
511
+ };
512
+ const getRestrictedSchema = (path, schema2) => {
513
+ let restricted = getRestrictedSchemaBase(path, schema2);
514
+ restricted = restricted.nullable();
515
+ const description = schema2.describe();
516
+ if (description.oneOf.length > 0) {
517
+ restricted = restricted.oneOf(description.oneOf);
518
+ }
519
+ if (description.notOneOf.length > 0) {
520
+ restricted = restricted.notOneOf(description.notOneOf);
521
+ }
522
+ return restricted;
523
+ };
524
+ for (const [key, value] of Object.entries(configOverride)) {
525
+ if (value === void 0) continue;
526
+ const subSchema = getSubSchema(schema, key);
527
+ if (!subSchema) {
528
+ return Result.error(`The key ${JSON.stringify(key)} is not valid for the schema.`);
529
+ }
530
+ let restrictedSchema = getRestrictedSchema(key, subSchema);
531
+ try {
532
+ await restrictedSchema.validate(value, {
533
+ strict: true,
534
+ ...{
535
+ // Although `path` is not part of the yup types, it is actually recognized and does the correct thing
536
+ path: key
537
+ },
538
+ context: {
539
+ noUnknownPathPrefixes: [""]
540
+ }
541
+ });
542
+ } catch (error) {
543
+ if (error instanceof yup.ValidationError) {
544
+ return Result.error(error.message);
545
+ }
546
+ throw error;
547
+ }
548
+ }
549
+ return Result.ok(null);
550
+ }
551
+ async function assertNoConfigOverrideErrors(schema, config, options = {}) {
552
+ const res = await getConfigOverrideErrors(schema, config, options);
553
+ if (res.status === "error") throw new StackAssertionError(`Config override is invalid \u2014 at a place where it should have already been validated! ${res.error}`, { options, config, schema });
554
+ }
555
+ typeAssertIs()();
556
+ typeAssertExtends()();
557
+ typeAssertExtends()();
558
+ async function getIncompleteConfigWarnings(schema, incompleteConfig) {
559
+ await assertNoConfigOverrideErrors(schema, incompleteConfig, { allowPropertiesThatCanNoLongerBeOverridden: true });
560
+ let normalized;
561
+ try {
562
+ normalized = normalize(incompleteConfig, { onDotIntoNull: "empty-object" });
563
+ } catch (error) {
564
+ if (error instanceof NormalizationError) {
565
+ return Result.error(`Config is not normalizable. ` + error.message);
566
+ }
567
+ throw error;
568
+ }
569
+ try {
570
+ await schema.validate(normalized, {
571
+ strict: true,
572
+ context: {
573
+ noUnknownPathPrefixes: [""]
574
+ }
575
+ });
576
+ return Result.ok(null);
577
+ } catch (error) {
578
+ if (error instanceof yup.ValidationError) {
579
+ return Result.error(error.message);
580
+ }
581
+ throw error;
582
+ }
583
+ }
584
+ typeAssertExtends()();
585
+ typeAssertExtends()();
586
+ typeAssertExtends()();
587
+ typeAssertExtends()();
588
+ typeAssert()();
589
+ typeAssert()();
590
+ typeAssertExtends()();
218
591
  export {
219
- applyDefaults,
220
- branchConfigDefaults,
592
+ applyBranchDefaults,
593
+ applyEnvironmentDefaults,
594
+ applyOrganizationDefaults,
595
+ applyProjectDefaults,
596
+ assertNoConfigOverrideErrors,
221
597
  branchConfigSchema,
222
598
  configLevels,
223
- environmentConfigDefaults,
224
599
  environmentConfigSchema,
225
- organizationConfigDefaults,
600
+ getConfigOverrideErrors,
601
+ getIncompleteConfigWarnings,
602
+ migrateConfigOverride,
226
603
  organizationConfigSchema,
227
- projectConfigDefaults,
228
- projectConfigSchema
604
+ projectConfigSchema,
605
+ sanitizeBranchConfig,
606
+ sanitizeEnvironmentConfig,
607
+ sanitizeOrganizationConfig,
608
+ sanitizeProjectConfig
229
609
  };
230
610
  //# sourceMappingURL=schema.js.map