@tailor-platform/sdk 2.2.0 → 2.4.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.
- package/CHANGELOG.md +183 -0
- package/README.md +1 -1
- package/dist/{application-BpLeawBg.mjs → application-C8a0UJ-z.mjs} +312 -166
- package/dist/application-C8a0UJ-z.mjs.map +1 -0
- package/dist/application-D4zOiLLa.mjs +3 -0
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +47 -24
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +6 -0
- package/dist/cli/commands/tailordb/migrate/rename-detection.d.mts +22 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +1 -1
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +30 -11
- package/dist/cli/commands/tailordb/truncate.d.mts +2 -2
- package/dist/cli/lib.mjs +2 -2
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +366 -137
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/ts-hook.mjs +55 -8
- package/dist/completion/zsh-worker.zsh +38 -10
- package/dist/configure/config/index.d.mts +1 -1
- package/dist/configure/index.d.mts +3 -2
- package/dist/configure/index.mjs +92 -25
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/aigateway/index.d.mts +5 -4
- package/dist/configure/services/aigateway/types.d.mts +6 -1
- package/dist/configure/services/auth/types.d.mts +2 -2
- package/dist/configure/services/index.d.mts +2 -2
- package/dist/configure/services/tailordb/permission.d.mts +1 -1
- package/dist/configure/services/tailordb/schema.d.mts +1 -1
- package/dist/configure/services/tailordb/types.d.mts +7 -4
- package/dist/configure/services/workflow/index.d.mts +2 -2
- package/dist/configure/services/workflow/wait-point.d.mts +72 -13
- package/dist/configure/types/auth-namespace-name.d.mts +18 -0
- package/dist/{crashreport-CINZ5dVN.mjs → crashreport-Cd4TqfZq.mjs} +1 -1
- package/dist/{crashreport-CIIfLnoO.mjs → crashreport-Q0pDAlrj.mjs} +8 -4
- package/dist/{crashreport-CIIfLnoO.mjs.map → crashreport-Q0pDAlrj.mjs.map} +1 -1
- package/dist/{errors-CWj21238.mjs → errors-Bxut5_Sy.mjs} +17 -3
- package/dist/{errors-CWj21238.mjs.map → errors-Bxut5_Sy.mjs.map} +1 -1
- package/dist/file-BUcmVIH2.mjs.map +1 -1
- package/dist/kysely/index.d.mts +2 -1
- package/dist/kysely/index.mjs +2 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/{logger-DSdExfor.mjs → logger-D0bEznPv.mjs} +9 -5
- package/dist/{logger-DSdExfor.mjs.map → logger-D0bEznPv.mjs.map} +1 -1
- package/dist/package-json-8b0O9TlX.mjs.map +1 -1
- package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
- package/dist/plugin/builtin/enum-constants/index.mjs +5 -5
- package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.mjs +14 -11
- package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
- package/dist/plugin/builtin/kysely-type/index.mjs +9 -9
- package/dist/plugin/builtin/kysely-type/index.mjs.map +1 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -4
- package/dist/plugin/get-generated-type.d.mts +4 -4
- package/dist/plugin/index.mjs +13 -13
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/types.d.mts +2 -2
- package/dist/plugin/with-context.d.mts +1 -1
- package/dist/{register-ts-hook-CTth1eqj.mjs → register-ts-hook-Vqjza10T.mjs} +2516 -857
- package/dist/register-ts-hook-Vqjza10T.mjs.map +1 -0
- package/dist/{repl-editor-BCozyiNq.mjs → repl-editor-F9LotMNx.mjs} +3 -2
- package/dist/{repl-editor-BCozyiNq.mjs.map → repl-editor-F9LotMNx.mjs.map} +1 -1
- package/dist/runtime/file.d.mts +14 -14
- package/dist/{schema--xYWRGfe.mjs → schema-D4TC4BC3.mjs} +4 -3
- package/dist/schema-D4TC4BC3.mjs.map +1 -0
- package/dist/seed/index.d.mts +4 -4
- package/dist/seed/index.mjs +3 -3
- package/dist/seed/index.mjs.map +1 -1
- package/dist/{seed-xqM9WIHP.mjs → seed-Cibjmtry.mjs} +46 -40
- package/dist/seed-Cibjmtry.mjs.map +1 -0
- package/dist/{service-goqlJJgA.mjs → service-2grcAOCJ.mjs} +61 -27
- package/dist/service-2grcAOCJ.mjs.map +1 -0
- package/dist/service-CaBPNDIZ.mjs +3 -0
- package/dist/{service-CCwl3Avt.mjs → service-LTfp03ZZ.mjs} +7 -5
- package/dist/{service-CCwl3Avt.mjs.map → service-LTfp03ZZ.mjs.map} +1 -1
- package/dist/types/aigateway.generated.d.mts +2 -2
- package/dist/types/auth.generated.d.mts +3 -3
- package/dist/types/executor.generated.d.mts +1 -1
- package/dist/types/tailordb.generated.d.mts +12 -12
- package/dist/utils/test/index.d.mts +4 -4
- package/dist/utils/test/index.mjs +4 -4
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/index.mjs +95 -7
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/workflow.d.mts +13 -1
- package/dist/wait-point-invoker-__oE88_P.mjs +148 -0
- package/dist/wait-point-invoker-__oE88_P.mjs.map +1 -0
- package/dist/wait-point-registry-TL99zotw.mjs +47 -0
- package/dist/wait-point-registry-TL99zotw.mjs.map +1 -0
- package/docs/cli/application.md +3 -1
- package/docs/cli/function.md +17 -13
- package/docs/cli/setup.md +14 -1
- package/docs/cli/tailordb.md +30 -27
- package/docs/cli/workspace.md +2 -2
- package/docs/cli-reference.md +23 -22
- package/docs/configuration.md +2 -2
- package/docs/github-actions.md +73 -28
- package/docs/migration/v2.md +58 -4
- package/docs/migration/v3.md +41 -0
- package/docs/quickstart.md +1 -1
- package/docs/services/aigateway.md +33 -9
- package/docs/services/executor.md +5 -1
- package/docs/services/tailordb-migration.md +146 -44
- package/docs/services/tailordb.md +7 -7
- package/docs/services/workflow.md +52 -2
- package/docs/testing.md +13 -1
- package/package.json +8 -8
- package/dist/application-BpLeawBg.mjs.map +0 -1
- package/dist/application-BxLLiLsr.mjs +0 -3
- package/dist/register-ts-hook-CTth1eqj.mjs.map +0 -1
- package/dist/schema--xYWRGfe.mjs.map +0 -1
- package/dist/seed-xqM9WIHP.mjs.map +0 -1
- package/dist/service-_XmjSEGr.mjs +0 -3
- package/dist/service-goqlJJgA.mjs.map +0 -1
- package/dist/test-env-key-D7UkZp99.mjs +0 -75
- package/dist/test-env-key-D7UkZp99.mjs.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { n as isSdkBranded } from "./brand-Eo4pLXPJ.mjs";
|
|
2
|
+
import { n as getScopedWaitPoints } from "./wait-point-registry-TL99zotw.mjs";
|
|
2
3
|
import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
|
|
3
|
-
import { a as parseBoolean, n as logger, r as styles } from "./logger-
|
|
4
|
-
import { a as AuthConfigSchema, c as stripTailorDBTypeBuilderHelpers, d as applyRelationMetadataToFieldConfig, f as buildRelationInfo,
|
|
4
|
+
import { a as parseBoolean, n as logger, r as styles } from "./logger-D0bEznPv.mjs";
|
|
5
|
+
import { a as AuthConfigSchema, b as functionSchema, c as stripTailorDBTypeBuilderHelpers, d as applyRelationMetadataToFieldConfig, f as buildRelationInfo, h as loadFilesWithIgnores, l as TailorDBServiceConfigSchema, m as validateRelationConfig, n as loadExecutor, o as AuthInvokerSchema, p as processRelationMetadata, s as TailorFieldSchema, t as createExecutorService, u as TailorDBTypeSchema, y as importUserModule } from "./service-2grcAOCJ.mjs";
|
|
5
6
|
import { t as multiline } from "./multiline-sfHpTZZK.mjs";
|
|
6
7
|
import { t as userAgent } from "./user-agent-Bgsszb5I.mjs";
|
|
7
|
-
import { t as createCLIError } from "./errors-
|
|
8
|
+
import { t as createCLIError } from "./errors-Bxut5_Sy.mjs";
|
|
8
9
|
import { n as isNodeBuiltinImport, t as getNodeBuiltinMessage } from "./node-builtins-CmaL2Cbq.mjs";
|
|
9
10
|
import { t as isPluginGeneratedType } from "./type-source-DH_LH20p.mjs";
|
|
10
11
|
import { createRequire } from "node:module";
|
|
@@ -16,7 +17,6 @@ import { create } from "@bufbuild/protobuf";
|
|
|
16
17
|
import * as fs from "node:fs";
|
|
17
18
|
import { readFileSync } from "node:fs";
|
|
18
19
|
import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";
|
|
19
|
-
import { pathToFileURL } from "node:url";
|
|
20
20
|
import { OAuth2Client } from "@badgateway/oauth2-client";
|
|
21
21
|
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
|
22
22
|
import pLimit from "p-limit";
|
|
@@ -191,7 +191,8 @@ async function initOperatorClient(accessToken, config) {
|
|
|
191
191
|
createTracingInterceptor(),
|
|
192
192
|
concurrencyLimitInterceptor()
|
|
193
193
|
];
|
|
194
|
-
|
|
194
|
+
const transport = await createTransport(getPlatformBaseUrl(platformConfig), interceptors);
|
|
195
|
+
return createClient(OperatorService, transport);
|
|
195
196
|
}
|
|
196
197
|
/**
|
|
197
198
|
* Create a Connect transport using connect-node (HTTP/2).
|
|
@@ -253,7 +254,7 @@ function retryInterceptor() {
|
|
|
253
254
|
return (next) => async (req) => {
|
|
254
255
|
if (req.stream) return await next(req);
|
|
255
256
|
let lastError;
|
|
256
|
-
for (let i = 0; i <
|
|
257
|
+
for (let i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
|
|
257
258
|
if (i > 0) await waitRetryBackoff(i);
|
|
258
259
|
try {
|
|
259
260
|
return await next(req);
|
|
@@ -263,7 +264,7 @@ function retryInterceptor() {
|
|
|
263
264
|
logger.debug(`retry: ${req.method.name} returned AlreadyExists on attempt ${i + 1}; treating as success (prior attempt likely committed)`);
|
|
264
265
|
return synthesizeEmptyUnaryResponse(req);
|
|
265
266
|
}
|
|
266
|
-
const { reportCrash } = await import("./crashreport-
|
|
267
|
+
const { reportCrash } = await import("./crashreport-Cd4TqfZq.mjs");
|
|
267
268
|
await reportCrash(error, "handledError");
|
|
268
269
|
}
|
|
269
270
|
if (req.method.name !== "CreateWorkspace" && isRetirable(error, req.method.idempotency)) {
|
|
@@ -397,6 +398,8 @@ function synthesizeEmptyUnaryResponse(req) {
|
|
|
397
398
|
* response was lost), which is what triggers the `already_exists` race.
|
|
398
399
|
*/
|
|
399
400
|
const RETRY_BASE_DELAY_MS = 500;
|
|
401
|
+
/** Maximum number of attempts, including the initial one, for a retried request. */
|
|
402
|
+
const MAX_RETRY_ATTEMPTS = 3;
|
|
400
403
|
/**
|
|
401
404
|
* Wait for an exponential backoff delay with jitter.
|
|
402
405
|
* @param attempt - Current retry attempt number (1-based)
|
|
@@ -424,6 +427,7 @@ function isRetirable(error, idempotency) {
|
|
|
424
427
|
}
|
|
425
428
|
/**
|
|
426
429
|
* Create an interceptor that enhances error messages from the Operator API.
|
|
430
|
+
* @internal
|
|
427
431
|
* @returns Error handling interceptor
|
|
428
432
|
*/
|
|
429
433
|
function errorHandlingInterceptor() {
|
|
@@ -433,8 +437,8 @@ function errorHandlingInterceptor() {
|
|
|
433
437
|
} catch (error) {
|
|
434
438
|
if (error instanceof ConnectError) {
|
|
435
439
|
const { operation, resourceType } = parseMethodName(req.method.name);
|
|
436
|
-
const
|
|
437
|
-
throw new ConnectError(`Failed to ${operation} ${resourceType}: ${error.rawMessage}
|
|
440
|
+
const identity = formatRequestIdentity(req.message, req.method.name);
|
|
441
|
+
throw new ConnectError(`Failed to ${operation} ${resourceType}${identity}: ${error.rawMessage}`, error.code, error.metadata);
|
|
438
442
|
}
|
|
439
443
|
throw error;
|
|
440
444
|
}
|
|
@@ -457,28 +461,45 @@ function parseMethodName(methodName) {
|
|
|
457
461
|
resourceType: resource
|
|
458
462
|
};
|
|
459
463
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
const IDENTITY_KEYS = /* @__PURE__ */ new Set(["name", "id"]);
|
|
465
|
+
const IDENTITY_KEY_SUFFIXES = [
|
|
466
|
+
"Name",
|
|
467
|
+
"Id",
|
|
468
|
+
"Namespace"
|
|
469
|
+
];
|
|
470
|
+
const NESTED_IDENTITY_KEY = "name";
|
|
471
|
+
const METHOD_IDENTITY_KEYS = {
|
|
472
|
+
GetMetadata: ["trn"],
|
|
473
|
+
SetMetadata: ["trn"],
|
|
474
|
+
AddCustomDomain: ["domain"],
|
|
475
|
+
GetCustomDomain: ["domain"],
|
|
476
|
+
RemoveCustomDomain: ["domain"],
|
|
477
|
+
CreateWorkflowJobFunctionExecutionPolicy: ["executionPolicyKey"],
|
|
478
|
+
UpdateWorkflowJobFunctionExecutionPolicy: ["executionPolicyKey"],
|
|
479
|
+
GetWorkflowJobFunctionExecutionPolicyByKey: ["executionPolicyKey"]
|
|
480
|
+
};
|
|
481
|
+
function isIdentityKey(key, methodName) {
|
|
482
|
+
if (key.startsWith("$")) return false;
|
|
483
|
+
return IDENTITY_KEYS.has(key) || IDENTITY_KEY_SUFFIXES.some((suffix) => key.endsWith(suffix)) || (METHOD_IDENTITY_KEYS[methodName]?.includes(key) ?? false);
|
|
469
484
|
}
|
|
470
485
|
/**
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
486
|
+
* Format an allowlisted identity suffix for enhanced error messages.
|
|
487
|
+
*
|
|
488
|
+
* Only resource identifiers are included — the rest of the request payload
|
|
489
|
+
* can carry credentials and must never reach terminal or CI logs.
|
|
490
|
+
* @param message - Request message to extract identifiers from
|
|
491
|
+
* @param methodName - RPC method name used to resolve method-scoped identifiers
|
|
492
|
+
* @returns Identity suffix like " (namespaceName: x, type.name: y)", or an empty string
|
|
493
|
+
*/
|
|
494
|
+
function formatRequestIdentity(message, methodName) {
|
|
495
|
+
if (typeof message !== "object" || message === null) return "";
|
|
496
|
+
const parts = [];
|
|
497
|
+
for (const [key, value] of Object.entries(message)) if (typeof value === "string" && value !== "" && isIdentityKey(key, methodName)) parts.push(`${key}: ${value}`);
|
|
498
|
+
else if (!key.startsWith("$") && value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.$typeName === "string") {
|
|
499
|
+
const nestedName = value[NESTED_IDENTITY_KEY];
|
|
500
|
+
if (typeof nestedName === "string" && nestedName !== "") parts.push(`${key}.${NESTED_IDENTITY_KEY}: ${nestedName}`);
|
|
481
501
|
}
|
|
502
|
+
return parts.length === 0 ? "" : ` (${parts.join(", ")})`;
|
|
482
503
|
}
|
|
483
504
|
const MAX_PAGE_SIZE = 1e3;
|
|
484
505
|
/**
|
|
@@ -631,14 +652,15 @@ async function fetchMachineUserToken(url, clientId, clientSecret) {
|
|
|
631
652
|
formData.append("grant_type", "client_credentials");
|
|
632
653
|
formData.append("client_id", clientId);
|
|
633
654
|
formData.append("client_secret", clientSecret);
|
|
634
|
-
const
|
|
655
|
+
const request = {
|
|
635
656
|
method: "POST",
|
|
636
657
|
headers: {
|
|
637
658
|
"User-Agent": await userAgent(),
|
|
638
659
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
639
660
|
},
|
|
640
661
|
body: formData
|
|
641
|
-
}
|
|
662
|
+
};
|
|
663
|
+
const resp = await withConnectTimeoutRetry("machine user token request", () => fetch(tokenEndpoint, request));
|
|
642
664
|
if (!resp.ok) {
|
|
643
665
|
const body = await resp.text().catch(() => "");
|
|
644
666
|
throw new Error(`Failed to fetch machine user token: ${resp.status} ${resp.statusText} ${body.slice(0, 500)}`);
|
|
@@ -650,6 +672,29 @@ async function fetchMachineUserToken(url, clientId, clientSecret) {
|
|
|
650
672
|
expires_in: z.number()
|
|
651
673
|
}).parse(rawJson);
|
|
652
674
|
}
|
|
675
|
+
function isUndiciConnectTimeout(error) {
|
|
676
|
+
if (!(error instanceof TypeError)) return false;
|
|
677
|
+
const cause = error.cause;
|
|
678
|
+
return cause instanceof Error && "code" in cause && cause.code === "UND_ERR_CONNECT_TIMEOUT";
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Retry a request that failed before the connection was established.
|
|
682
|
+
*
|
|
683
|
+
* Only `UND_ERR_CONNECT_TIMEOUT` is retried: the request provably never reached
|
|
684
|
+
* the server, so replaying it cannot duplicate a server-side effect.
|
|
685
|
+
* @param label - Request description for the retry debug log
|
|
686
|
+
* @param send - Sends the request; called once per attempt
|
|
687
|
+
* @returns The first successful result
|
|
688
|
+
*/
|
|
689
|
+
async function withConnectTimeoutRetry(label, send) {
|
|
690
|
+
for (let attempt = 1;; attempt++) try {
|
|
691
|
+
return await send();
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (!isUndiciConnectTimeout(error) || attempt >= MAX_RETRY_ATTEMPTS) throw error;
|
|
694
|
+
logger.debug(`retry: ${label} attempt ${attempt} failed with UND_ERR_CONNECT_TIMEOUT; retrying`);
|
|
695
|
+
await waitRetryBackoff(attempt);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
653
698
|
/**
|
|
654
699
|
* Fetch an OAuth2 token for a platform machine user via client_credentials grant.
|
|
655
700
|
* @param clientId - Client ID for the platform machine user
|
|
@@ -658,12 +703,13 @@ async function fetchMachineUserToken(url, clientId, clientSecret) {
|
|
|
658
703
|
* @returns OAuth2 token
|
|
659
704
|
*/
|
|
660
705
|
async function fetchPlatformMachineUserToken(clientId, clientSecret, config) {
|
|
661
|
-
|
|
706
|
+
const server = getPlatformBaseUrl(config);
|
|
707
|
+
return await withConnectTimeoutRetry("platform machine user token request", () => new OAuth2Client({
|
|
662
708
|
clientId,
|
|
663
709
|
clientSecret,
|
|
664
|
-
server
|
|
710
|
+
server,
|
|
665
711
|
discoveryEndpoint: oauth2DiscoveryEndpoint
|
|
666
|
-
}).clientCredentials();
|
|
712
|
+
}).clientCredentials());
|
|
667
713
|
}
|
|
668
714
|
/**
|
|
669
715
|
* Close the global HTTP connection pool to prevent libuv UV_HANDLE_CLOSING
|
|
@@ -950,7 +996,7 @@ function isPluginExecutorWithFile(executor) {
|
|
|
950
996
|
* @param executors - Array of plugin executor information
|
|
951
997
|
* @param outputDir - Base output directory (e.g., .tailor)
|
|
952
998
|
* @param typeGenerationResult - Result from plugin type generation (for import resolution)
|
|
953
|
-
* @param sourceTypeInfoMap - Map of source
|
|
999
|
+
* @param sourceTypeInfoMap - Map of source table names to their source info
|
|
954
1000
|
* @param configPath - Path to tailor.config.ts (used for resolving plugin import paths)
|
|
955
1001
|
* @returns Array of generated file paths
|
|
956
1002
|
*/
|
|
@@ -971,7 +1017,7 @@ function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult,
|
|
|
971
1017
|
* @param info - Plugin executor metadata and definition
|
|
972
1018
|
* @param outputDir - Base output directory (e.g., .tailor)
|
|
973
1019
|
* @param typeGenerationResult - Result from plugin type generation
|
|
974
|
-
* @param sourceTypeInfoMap - Map of source
|
|
1020
|
+
* @param sourceTypeInfoMap - Map of source table names to their source info
|
|
975
1021
|
* @param baseDirs - Base directories for resolving plugin import paths
|
|
976
1022
|
* @returns Absolute path to the generated file
|
|
977
1023
|
*/
|
|
@@ -994,7 +1040,7 @@ function generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourc
|
|
|
994
1040
|
* @param executor - Executor definition with resolve
|
|
995
1041
|
* @param outputDir - Base output directory
|
|
996
1042
|
* @param typeGenerationResult - Result from plugin type generation
|
|
997
|
-
* @param sourceTypeInfoMap - Map of source
|
|
1043
|
+
* @param sourceTypeInfoMap - Map of source table names to their source info
|
|
998
1044
|
* @param baseDirs - Base directories for resolving plugin import paths
|
|
999
1045
|
* @returns TypeScript source code for executor file
|
|
1000
1046
|
*/
|
|
@@ -1029,7 +1075,7 @@ function generateExecutorFileContentNew(info, executor, outputDir, typeGeneratio
|
|
|
1029
1075
|
* @param outputDir - Base output directory for generated files
|
|
1030
1076
|
* @param pluginId - Plugin identifier used for output paths
|
|
1031
1077
|
* @param typeGenerationResult - Result from plugin type generation
|
|
1032
|
-
* @param sourceTypeInfoMap - Map of source
|
|
1078
|
+
* @param sourceTypeInfoMap - Map of source table names to their source info
|
|
1033
1079
|
* @returns Map of context keys to their import information
|
|
1034
1080
|
*/
|
|
1035
1081
|
function collectTypeImports(context, outputDir, pluginId, typeGenerationResult, sourceTypeInfoMap) {
|
|
@@ -1076,9 +1122,9 @@ function generateContextCode(context, typeImports) {
|
|
|
1076
1122
|
return `{\n${entries.join(",\n")},\n}`;
|
|
1077
1123
|
}
|
|
1078
1124
|
/**
|
|
1079
|
-
* Check if a value is a TailorDB
|
|
1125
|
+
* Check if a value is a TailorDB table object.
|
|
1080
1126
|
* @param value - Value to inspect
|
|
1081
|
-
* @returns True if value is a
|
|
1127
|
+
* @returns True if value is a table object with name and fields
|
|
1082
1128
|
*/
|
|
1083
1129
|
function isTypeObject(value) {
|
|
1084
1130
|
return typeof value === "object" && value !== null && "name" in value && "fields" in value && typeof value.name === "string";
|
|
@@ -1276,15 +1322,15 @@ function toKebabCase$1(str) {
|
|
|
1276
1322
|
/**
|
|
1277
1323
|
* Plugin Type Generator
|
|
1278
1324
|
*
|
|
1279
|
-
* Generates TypeScript files for plugin-generated types (TailorDB
|
|
1280
|
-
* These files can be imported by plugin executors to reference generated
|
|
1325
|
+
* Generates TypeScript files for plugin-generated types (TailorDB tables).
|
|
1326
|
+
* These files can be imported by plugin executors to reference generated tables.
|
|
1281
1327
|
*/
|
|
1282
1328
|
function isFieldDefinition(value) {
|
|
1283
1329
|
return typeof value === "object" && value !== null;
|
|
1284
1330
|
}
|
|
1285
1331
|
/**
|
|
1286
|
-
* Generate TypeScript files for plugin-generated
|
|
1287
|
-
* These files export the
|
|
1332
|
+
* Generate TypeScript files for plugin-generated tables.
|
|
1333
|
+
* These files export the table definition and can be imported by executor files.
|
|
1288
1334
|
* @param types - Array of plugin type information
|
|
1289
1335
|
* @param outputDir - Base output directory (e.g., .tailor)
|
|
1290
1336
|
* @returns Generation result with file paths
|
|
@@ -1299,7 +1345,7 @@ function generatePluginTypeFiles(types, outputDir) {
|
|
|
1299
1345
|
const seenTypeNames = /* @__PURE__ */ new Map();
|
|
1300
1346
|
for (const info of types) {
|
|
1301
1347
|
const existing = seenTypeNames.get(info.type.name);
|
|
1302
|
-
if (existing) throw new Error(`Duplicate plugin-generated
|
|
1348
|
+
if (existing) throw new Error(`Duplicate plugin-generated table name "${info.type.name}" detected. First: plugin "${existing.pluginId}" (kind: "${existing.kind}", source: "${existing.sourceTypeName}"), Second: plugin "${info.pluginId}" (kind: "${info.kind}", source: "${info.sourceTypeName}"). Plugin-generated table names must be unique.`);
|
|
1303
1349
|
seenTypeNames.set(info.type.name, info);
|
|
1304
1350
|
const pluginDir = sanitizePluginId(info.pluginId);
|
|
1305
1351
|
const typeOutputDir = path.join(outputDir, pluginDir, "types");
|
|
@@ -1330,8 +1376,8 @@ function generateTypeFileContent(info) {
|
|
|
1330
1376
|
const fieldsCode = generateFieldsCode(type);
|
|
1331
1377
|
return multiline`
|
|
1332
1378
|
/**
|
|
1333
|
-
* Auto-generated
|
|
1334
|
-
* Source
|
|
1379
|
+
* Auto-generated table by plugin: ${pluginId}
|
|
1380
|
+
* Source: ${sourceTypeName}
|
|
1335
1381
|
* Kind: ${kind}
|
|
1336
1382
|
*
|
|
1337
1383
|
* DO NOT EDIT - This file is generated by @tailor-platform/sdk
|
|
@@ -1345,8 +1391,8 @@ function generateTypeFileContent(info) {
|
|
|
1345
1391
|
}
|
|
1346
1392
|
/**
|
|
1347
1393
|
* Generate TypeScript code for field definitions.
|
|
1348
|
-
* This creates a simplified version of the
|
|
1349
|
-
* @param type - TailorDB
|
|
1394
|
+
* This creates a simplified version of the table's fields.
|
|
1395
|
+
* @param type - TailorDB table
|
|
1350
1396
|
* @returns TypeScript code for fields object
|
|
1351
1397
|
*/
|
|
1352
1398
|
function generateFieldsCode(type) {
|
|
@@ -1359,7 +1405,7 @@ function generateFieldsCode(type) {
|
|
|
1359
1405
|
return `{\n${fieldEntries.join(",\n")},\n}`;
|
|
1360
1406
|
}
|
|
1361
1407
|
/**
|
|
1362
|
-
* Map from TailorDB
|
|
1408
|
+
* Map from TailorDB table to SDK method name.
|
|
1363
1409
|
*/
|
|
1364
1410
|
const typeToMethodMap = {
|
|
1365
1411
|
string: "string",
|
|
@@ -1602,7 +1648,8 @@ function collectSdkBindings(program, functionName) {
|
|
|
1602
1648
|
if (nodeType === "VariableDeclaration") {
|
|
1603
1649
|
const varDecl = node;
|
|
1604
1650
|
for (const decl of varDecl.declarations) {
|
|
1605
|
-
const
|
|
1651
|
+
const init = unwrapAwait(decl.init);
|
|
1652
|
+
const source = getImportSource(init);
|
|
1606
1653
|
if (source && isTailorSdkSource(source)) {
|
|
1607
1654
|
const id = decl.id;
|
|
1608
1655
|
if (id.type === "Identifier") bindings.add(`__namespace__:${id.name}`);
|
|
@@ -1732,7 +1779,8 @@ function findAllWorkflows(program, _sourceText) {
|
|
|
1732
1779
|
const args = node.arguments;
|
|
1733
1780
|
const firstArg = args[0];
|
|
1734
1781
|
if (args.length >= 1 && firstArg?.type === "ObjectExpression") {
|
|
1735
|
-
const
|
|
1782
|
+
const configObj = assertDefined(firstArg, "createWorkflow first argument missing");
|
|
1783
|
+
const nameProp = findProperty(configObj.properties, "name");
|
|
1736
1784
|
if (nameProp && isStringLiteral(nameProp.value)) {
|
|
1737
1785
|
let exportName;
|
|
1738
1786
|
let isDefaultExport = false;
|
|
@@ -2055,10 +2103,12 @@ function transformStartCalls(source, startContext, currentFilePath) {
|
|
|
2055
2103
|
let transformedCall;
|
|
2056
2104
|
if (call.kind === "workflow") {
|
|
2057
2105
|
let optionsPart = "";
|
|
2058
|
-
if (call.optionsText !== void 0)
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2106
|
+
if (call.optionsText !== void 0) {
|
|
2107
|
+
if (authNamespace) {
|
|
2108
|
+
optionsPart = `, ${NORMALIZER_IDENTIFIER}(${call.optionsText})`;
|
|
2109
|
+
needsNormalizerHelper = true;
|
|
2110
|
+
} else optionsPart = `, ${call.optionsText}`;
|
|
2111
|
+
}
|
|
2062
2112
|
transformedCall = `tailor.workflow.startWorkflow(${JSON.stringify(call.targetName)}, ${call.argsText || "undefined"}${optionsPart})`;
|
|
2063
2113
|
} else {
|
|
2064
2114
|
const optionsPart = call.optionsText !== void 0 ? `, ${call.optionsText}` : "";
|
|
@@ -2112,11 +2162,14 @@ function createBundleLog(options = {}) {
|
|
|
2112
2162
|
}
|
|
2113
2163
|
function unresolvedImportError(logs, tsconfig) {
|
|
2114
2164
|
const imports = logs.map(formatUnresolvedImport);
|
|
2165
|
+
const message = imports.length === 1 ? `Could not resolve ${imports[0]}.` : `Could not resolve ${imports.length} imports.`;
|
|
2166
|
+
const details = imports.length === 1 ? void 0 : imports.map((item) => `- ${item}`).join("\n");
|
|
2167
|
+
const suggestion = unresolvedImportSuggestion(logs, tsconfig);
|
|
2115
2168
|
return createCLIError({
|
|
2116
2169
|
code: "UNRESOLVED_IMPORT",
|
|
2117
|
-
message
|
|
2118
|
-
details
|
|
2119
|
-
suggestion
|
|
2170
|
+
message,
|
|
2171
|
+
details,
|
|
2172
|
+
suggestion
|
|
2120
2173
|
});
|
|
2121
2174
|
}
|
|
2122
2175
|
function unresolvedImportSuggestion(logs, tsconfig) {
|
|
@@ -2417,19 +2470,22 @@ async function bundleAuthHooks(options) {
|
|
|
2417
2470
|
const absoluteConfigPath = path.resolve(configPath);
|
|
2418
2471
|
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
2419
2472
|
const functionName = `auth-hook--${authName}--before-login`;
|
|
2473
|
+
const serializedStartContext = serializeStartContext(startContext);
|
|
2474
|
+
const sortedEnvPrefix = JSON.stringify(Object.fromEntries(Object.entries(env).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
2475
|
+
const contextHash = computeBundlerContextHash({
|
|
2476
|
+
sourceFile: absoluteConfigPath,
|
|
2477
|
+
extraContext: serializedStartContext,
|
|
2478
|
+
tsconfig,
|
|
2479
|
+
inlineSourcemap,
|
|
2480
|
+
bundleLogLevel,
|
|
2481
|
+
prefix: sortedEnvPrefix
|
|
2482
|
+
});
|
|
2420
2483
|
const code = await withCache({
|
|
2421
2484
|
cache,
|
|
2422
2485
|
kind: "auth-hook",
|
|
2423
2486
|
name: functionName,
|
|
2424
2487
|
sourceFile: absoluteConfigPath,
|
|
2425
|
-
contextHash
|
|
2426
|
-
sourceFile: absoluteConfigPath,
|
|
2427
|
-
extraContext: serializeStartContext(startContext),
|
|
2428
|
-
tsconfig,
|
|
2429
|
-
inlineSourcemap,
|
|
2430
|
-
bundleLogLevel,
|
|
2431
|
-
prefix: JSON.stringify(Object.fromEntries(Object.entries(env).toSorted(([a], [b]) => a.localeCompare(b))))
|
|
2432
|
-
}),
|
|
2488
|
+
contextHash,
|
|
2433
2489
|
async build(cachePlugins, trackDependency) {
|
|
2434
2490
|
const entryContent = multiline`
|
|
2435
2491
|
import _config from "${absoluteConfigPath}";
|
|
@@ -2579,6 +2635,7 @@ const tailorPrincipalMap = makePrincipalExpr({
|
|
|
2579
2635
|
attributeList: "$raw.attributes ?? []"
|
|
2580
2636
|
}
|
|
2581
2637
|
});
|
|
2638
|
+
const PRINCIPAL_VAR = "_principal";
|
|
2582
2639
|
/**
|
|
2583
2640
|
* Parse `wrapped` and return the first property of the top-level parenthesized
|
|
2584
2641
|
* object expression, or `undefined` if it does not parse as one.
|
|
@@ -2630,17 +2687,20 @@ function formatScriptContext(kind, context) {
|
|
|
2630
2687
|
const convertToScriptExpr = (fn, kind, context) => {
|
|
2631
2688
|
const precompiledExpr = getPrecompiledScriptExpr(fn);
|
|
2632
2689
|
if (precompiledExpr) return precompiledExpr;
|
|
2633
|
-
|
|
2690
|
+
const normalized = stringifyFunction(fn);
|
|
2691
|
+
return assertParsableExpression(`(${normalized})(${kind === "validate" ? `{ value: _value }` : kind === "hooks.create" ? `{ input: _value, invoker: ${PRINCIPAL_VAR}, now: _now }` : `{ input: _value, oldValue: _oldValue, invoker: ${PRINCIPAL_VAR}, now: _now }`})`, formatScriptContext(kind, context));
|
|
2634
2692
|
};
|
|
2635
2693
|
const convertTypeHookToExpr = (fn) => {
|
|
2636
2694
|
const precompiledExpr = getPrecompiledScriptExpr(fn);
|
|
2637
2695
|
if (precompiledExpr) return precompiledExpr;
|
|
2638
|
-
|
|
2696
|
+
const normalized = stringifyFunction(fn);
|
|
2697
|
+
return assertParsableExpression(`(${normalized})({ input: _input, oldRecord: _oldRecord, invoker: ${PRINCIPAL_VAR}, now: _now })`, "type-hook");
|
|
2639
2698
|
};
|
|
2640
2699
|
const convertTypeValidateToExpr = (fn) => {
|
|
2641
2700
|
const precompiledExpr = getPrecompiledScriptExpr(fn);
|
|
2642
2701
|
if (precompiledExpr) return precompiledExpr;
|
|
2643
|
-
|
|
2702
|
+
const normalized = stringifyFunction(fn);
|
|
2703
|
+
return assertParsableExpression(`(${normalized})({ newRecord: _newRecord, oldRecord: _oldRecord, invoker: ${PRINCIPAL_VAR} }, __issues)`, "type-validate");
|
|
2644
2704
|
};
|
|
2645
2705
|
/**
|
|
2646
2706
|
* Parse TailorDBField into OperatorFieldConfig.
|
|
@@ -2653,8 +2713,8 @@ function parseFieldConfig(field, context) {
|
|
|
2653
2713
|
const metadata = field.metadata;
|
|
2654
2714
|
const fieldType = field.type;
|
|
2655
2715
|
const rawRelation = field.rawRelation;
|
|
2656
|
-
if (context && context.fieldPath.length > 1 && metadata.default !== void 0) throw new Error(`Field "${context.fieldPath.join(".")}" on
|
|
2657
|
-
if (context && context.fieldPath.length > 1 && metadata.hooks) throw new Error(`Field "${context.fieldPath.join(".")}" on
|
|
2716
|
+
if (context && context.fieldPath.length > 1 && metadata.default !== void 0) throw new Error(`Field "${context.fieldPath.join(".")}" on table "${context.typeName}": .default() cannot be used on nested inner fields`);
|
|
2717
|
+
if (context && context.fieldPath.length > 1 && metadata.hooks) throw new Error(`Field "${context.fieldPath.join(".")}" on table "${context.typeName}": .hooks() cannot be used on nested inner fields`);
|
|
2658
2718
|
const nestedFields = field.fields;
|
|
2659
2719
|
return {
|
|
2660
2720
|
type: fieldType,
|
|
@@ -2715,7 +2775,7 @@ function isSingleArrayConditionFormat(cond) {
|
|
|
2715
2775
|
}
|
|
2716
2776
|
/**
|
|
2717
2777
|
* Normalize record-level permissions into a standard structure.
|
|
2718
|
-
* @param permission - Tailor
|
|
2778
|
+
* @param permission - Tailor table permission
|
|
2719
2779
|
* @returns Normalized record permissions
|
|
2720
2780
|
*/
|
|
2721
2781
|
function normalizePermission(permission) {
|
|
@@ -2817,8 +2877,8 @@ function findOmittedPermitRules(rawPermissions) {
|
|
|
2817
2877
|
return locations;
|
|
2818
2878
|
}
|
|
2819
2879
|
/**
|
|
2820
|
-
* Check whether GraphQL exposure is fully disabled for a
|
|
2821
|
-
* effective gqlOperations (the
|
|
2880
|
+
* Check whether GraphQL exposure is fully disabled for a table, given its
|
|
2881
|
+
* effective gqlOperations (the table's own setting, falling back to the
|
|
2822
2882
|
* TailorDB namespace default). `undefined` means the default of all
|
|
2823
2883
|
* operations enabled, so it is never considered fully disabled.
|
|
2824
2884
|
* @param gqlOperations - Effective, normalized gqlOperations configuration
|
|
@@ -2829,15 +2889,15 @@ function isGqlOperationsFullyDisabled(gqlOperations) {
|
|
|
2829
2889
|
return gqlOperations.create === false && gqlOperations.update === false && gqlOperations.delete === false && gqlOperations.read === false;
|
|
2830
2890
|
}
|
|
2831
2891
|
/**
|
|
2832
|
-
* Find missing permission configuration for a TailorDB
|
|
2892
|
+
* Find missing permission configuration for a TailorDB table.
|
|
2833
2893
|
*
|
|
2834
2894
|
* Record-level permission is always required: TailorDB denies all record
|
|
2835
|
-
* operations for a
|
|
2895
|
+
* operations for a table without it, regardless of whether the table is
|
|
2836
2896
|
* exposed via GraphQL. GraphQL permission is required whenever GraphQL
|
|
2837
|
-
* exposure is enabled for the
|
|
2897
|
+
* exposure is enabled for the table (the default, unless every operation is
|
|
2838
2898
|
* explicitly disabled via `gqlOperations`).
|
|
2839
|
-
* @param rawPermissions - Raw permissions definition for the
|
|
2840
|
-
* @param effectiveGqlOperations - The
|
|
2899
|
+
* @param rawPermissions - Raw permissions definition for the table
|
|
2900
|
+
* @param effectiveGqlOperations - The table's own gqlOperations, falling back to the namespace default
|
|
2841
2901
|
* @returns Which permission configuration, if any, is missing
|
|
2842
2902
|
*/
|
|
2843
2903
|
function findMissingPermissionConfig(rawPermissions, effectiveGqlOperations) {
|
|
@@ -2850,12 +2910,12 @@ function findMissingPermissionConfig(rawPermissions, effectiveGqlOperations) {
|
|
|
2850
2910
|
//#endregion
|
|
2851
2911
|
//#region src/parser/service/tailordb/type-parser.ts
|
|
2852
2912
|
/**
|
|
2853
|
-
* Parse multiple TailorDB
|
|
2854
|
-
* This is the main entry point for parsing TailorDB
|
|
2855
|
-
* @param rawTypes - Raw TailorDB
|
|
2913
|
+
* Parse multiple TailorDB tables, build relationships, and validate uniqueness.
|
|
2914
|
+
* This is the main entry point for parsing TailorDB tables.
|
|
2915
|
+
* @param rawTypes - Raw TailorDB tables keyed by name
|
|
2856
2916
|
* @param namespace - TailorDB namespace name
|
|
2857
|
-
* @param typeSourceInfo - Optional
|
|
2858
|
-
* @returns Parsed
|
|
2917
|
+
* @param typeSourceInfo - Optional table source information
|
|
2918
|
+
* @returns Parsed tables
|
|
2859
2919
|
*/
|
|
2860
2920
|
function parseTypes(rawTypes, namespace, typeSourceInfo) {
|
|
2861
2921
|
const types = createRecord();
|
|
@@ -2867,11 +2927,11 @@ function parseTypes(rawTypes, namespace, typeSourceInfo) {
|
|
|
2867
2927
|
}
|
|
2868
2928
|
/**
|
|
2869
2929
|
* Parse a TailorDBTypeSchemaOutput into a TailorDBType.
|
|
2870
|
-
* @param type - TailorDB
|
|
2871
|
-
* @param allTypeNames - Set of all TailorDB
|
|
2872
|
-
* @param rawTypes - All raw TailorDB
|
|
2873
|
-
* @param typeSourceInfo - Optional
|
|
2874
|
-
* @returns Parsed TailorDB
|
|
2930
|
+
* @param type - TailorDB table to parse
|
|
2931
|
+
* @param allTypeNames - Set of all TailorDB table names
|
|
2932
|
+
* @param rawTypes - All raw TailorDB tables keyed by name
|
|
2933
|
+
* @param typeSourceInfo - Optional table source information
|
|
2934
|
+
* @returns Parsed TailorDB table
|
|
2875
2935
|
*/
|
|
2876
2936
|
function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
|
|
2877
2937
|
const metadata = type.metadata;
|
|
@@ -2896,12 +2956,12 @@ function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
|
|
|
2896
2956
|
"n-1",
|
|
2897
2957
|
"manyToOne",
|
|
2898
2958
|
"N-1"
|
|
2899
|
-
].includes(rawRelation.type) && fieldConfig.unique) throw new Error(`Field "${fieldName}" on
|
|
2959
|
+
].includes(rawRelation.type) && fieldConfig.unique) throw new Error(`Field "${fieldName}" on table "${type.name}": cannot set unique on n-1 (manyToOne) relation. Use 1-1 (oneToOne) relation instead, or remove the unique constraint.`);
|
|
2900
2960
|
const relationMetadata = processRelationMetadata(rawRelation, context, fieldConfig.array);
|
|
2901
2961
|
fieldConfig = applyRelationMetadataToFieldConfig(fieldConfig, relationMetadata);
|
|
2902
2962
|
}
|
|
2903
|
-
if (fieldConfig.array && fieldConfig.index) throw new Error(`Field "${fieldName}" on
|
|
2904
|
-
if (fieldConfig.array && fieldConfig.unique) throw new Error(`Field "${fieldName}" on
|
|
2963
|
+
if (fieldConfig.array && fieldConfig.index) throw new Error(`Field "${fieldName}" on table "${type.name}": index cannot be set on array fields`);
|
|
2964
|
+
if (fieldConfig.array && fieldConfig.unique) throw new Error(`Field "${fieldName}" on table "${type.name}": unique cannot be set on array fields`);
|
|
2905
2965
|
const parsedField = {
|
|
2906
2966
|
name: fieldName,
|
|
2907
2967
|
config: fieldConfig
|
|
@@ -2910,14 +2970,14 @@ function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
|
|
|
2910
2970
|
if (relationInfo) {
|
|
2911
2971
|
parsedField.relation = { ...relationInfo };
|
|
2912
2972
|
const forwardName = relationInfo.forwardName;
|
|
2913
|
-
if (forwardName.length === 0) throw new Error(`Forward relation name for field "${fieldName}" on
|
|
2973
|
+
if (forwardName.length === 0) throw new Error(`Forward relation name for field "${fieldName}" on table "${type.name}"${typeLocation} cannot be empty. Use the "as" option in .relation({ toward: { as: ... } }) to specify a non-empty name.`);
|
|
2914
2974
|
const existingForward = forwardRelationships[forwardName];
|
|
2915
|
-
if (existingForward) throw new Error(`Forward relation name "${forwardName}" on
|
|
2975
|
+
if (existingForward) throw new Error(`Forward relation name "${forwardName}" on table "${type.name}"${typeLocation} is duplicated between fields "${existingForward.targetField}" and "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a unique name.`);
|
|
2916
2976
|
if (Object.hasOwn(type.fields, forwardName)) {
|
|
2917
|
-
const message = forwardName === fieldName ? `Forward relation name "${forwardName}" on
|
|
2977
|
+
const message = forwardName === fieldName ? `Forward relation name "${forwardName}" on table "${type.name}"${typeLocation} is the same as its own relation field "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.` : `Forward relation name "${forwardName}" from field "${fieldName}" on table "${type.name}"${typeLocation} conflicts with existing field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`;
|
|
2918
2978
|
throw new Error(message);
|
|
2919
2979
|
}
|
|
2920
|
-
if (Object.hasOwn(metadata.files, forwardName)) throw new Error(`Forward relation name "${forwardName}" from field "${fieldName}" on
|
|
2980
|
+
if (Object.hasOwn(metadata.files, forwardName)) throw new Error(`Forward relation name "${forwardName}" from field "${fieldName}" on table "${type.name}"${typeLocation} conflicts with files field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`);
|
|
2921
2981
|
const targetType = rawTypes[relationInfo.targetType];
|
|
2922
2982
|
forwardRelationships[forwardName] = {
|
|
2923
2983
|
name: forwardName,
|
|
@@ -2949,11 +3009,11 @@ function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
|
|
|
2949
3009
|
};
|
|
2950
3010
|
}
|
|
2951
3011
|
/**
|
|
2952
|
-
* Build backward relationships between parsed
|
|
2953
|
-
* Also validates that backward relation names are unique within each
|
|
2954
|
-
* @param types - Parsed
|
|
3012
|
+
* Build backward relationships between parsed tables.
|
|
3013
|
+
* Also validates that backward relation names are unique within each table.
|
|
3014
|
+
* @param types - Parsed tables
|
|
2955
3015
|
* @param namespace - TailorDB namespace name
|
|
2956
|
-
* @param typeSourceInfo - Optional
|
|
3016
|
+
* @param typeSourceInfo - Optional table source information
|
|
2957
3017
|
*/
|
|
2958
3018
|
function buildBackwardRelationships(types, namespace, typeSourceInfo) {
|
|
2959
3019
|
const backwardNameSources = Object.create(null);
|
|
@@ -2993,25 +3053,25 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
|
|
|
2993
3053
|
const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, s.sourceType));
|
|
2994
3054
|
return `${s.sourceType}.${s.fieldName}${location}`;
|
|
2995
3055
|
}).join(", ");
|
|
2996
|
-
errors.push(`Backward relation name "${backwardName}" on
|
|
3056
|
+
errors.push(`Backward relation name "${backwardName}" on table "${targetTypeName}" is duplicated from: ${sourceList}. Use the "backward" option in .relation() to specify unique names.`);
|
|
2997
3057
|
}
|
|
2998
3058
|
if (Object.hasOwn(targetType.fields, backwardName)) {
|
|
2999
3059
|
const source = sources[0];
|
|
3000
3060
|
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3001
3061
|
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3002
|
-
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with existing field "${backwardName}" on
|
|
3062
|
+
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with existing field "${backwardName}" on table "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
|
|
3003
3063
|
}
|
|
3004
3064
|
if (targetType.files && Object.hasOwn(targetType.files, backwardName)) {
|
|
3005
3065
|
const source = sources[0];
|
|
3006
3066
|
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3007
3067
|
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3008
|
-
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with files field "${backwardName}" on
|
|
3068
|
+
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with files field "${backwardName}" on table "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
|
|
3009
3069
|
}
|
|
3010
3070
|
if (Object.hasOwn(targetType.forwardRelationships, backwardName)) {
|
|
3011
3071
|
const source = sources[0];
|
|
3012
3072
|
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3013
3073
|
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3014
|
-
errors.push(`Relation name "${backwardName}" on
|
|
3074
|
+
errors.push(`Relation name "${backwardName}" on table "${targetTypeName}"${targetLocation} is used by both a forward relationship and a backward relationship from ${source.sourceType}.${source.fieldName}${sourceLocation}. Use the "as" option in .relation({ toward: { as: ... } }) or the "backward" option in .relation() to specify unique names.`);
|
|
3015
3075
|
}
|
|
3016
3076
|
}
|
|
3017
3077
|
}
|
|
@@ -3020,11 +3080,11 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
|
|
|
3020
3080
|
/**
|
|
3021
3081
|
* Validate GraphQL query field name uniqueness.
|
|
3022
3082
|
* Checks for:
|
|
3023
|
-
* 1. Each
|
|
3024
|
-
* 2. No duplicate query names across all
|
|
3025
|
-
* @param types - Parsed
|
|
3083
|
+
* 1. Each table's singular query name != plural query name
|
|
3084
|
+
* 2. No duplicate query names across all tables
|
|
3085
|
+
* @param types - Parsed tables
|
|
3026
3086
|
* @param namespace - TailorDB namespace name
|
|
3027
|
-
* @param typeSourceInfo - Optional
|
|
3087
|
+
* @param typeSourceInfo - Optional table source information
|
|
3028
3088
|
*/
|
|
3029
3089
|
function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
|
|
3030
3090
|
const errors = [];
|
|
@@ -3032,7 +3092,7 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
|
|
|
3032
3092
|
const singularQuery = inflection.camelize(parsedType.name, true);
|
|
3033
3093
|
if (singularQuery === inflection.camelize(parsedType.pluralForm, true)) {
|
|
3034
3094
|
const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, parsedType.name));
|
|
3035
|
-
errors.push(`
|
|
3095
|
+
errors.push(`Table "${parsedType.name}"${location} has identical singular and plural query names "${singularQuery}". Use db.table(["${parsedType.name}", "UniquePluralForm"], {...}) to set a unique pluralForm.`);
|
|
3036
3096
|
}
|
|
3037
3097
|
}
|
|
3038
3098
|
const queryNameToSource = /* @__PURE__ */ new Map();
|
|
@@ -3106,12 +3166,12 @@ function collectBindingsFromPattern(pattern, bindings) {
|
|
|
3106
3166
|
else collectBindingsFromPattern(prop.value, bindings);
|
|
3107
3167
|
break;
|
|
3108
3168
|
case "ArrayPattern":
|
|
3109
|
-
for (const elem of pattern.elements) if (elem)
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
collectBindingsFromPattern(pattern.left, bindings);
|
|
3169
|
+
for (const elem of pattern.elements) if (elem) {
|
|
3170
|
+
if (elem.type === "RestElement") collectBindingsFromPattern(elem.argument, bindings);
|
|
3171
|
+
else collectBindingsFromPattern(elem, bindings);
|
|
3172
|
+
}
|
|
3114
3173
|
break;
|
|
3174
|
+
case "AssignmentPattern": collectBindingsFromPattern(pattern.left, bindings);
|
|
3115
3175
|
}
|
|
3116
3176
|
}
|
|
3117
3177
|
/** Fields that contain TypeScript type annotations (not runtime references). */
|
|
@@ -3386,7 +3446,7 @@ async function bundleScriptTarget(args) {
|
|
|
3386
3446
|
const context = `${kind} in ${sourceFilePath}`;
|
|
3387
3447
|
const fnSource = stringifyFunction(fn);
|
|
3388
3448
|
if ((kind === "typeValidate" || kind === "validate") && fn.constructor.name === "AsyncFunction") throw new Error(`${context} must be synchronous — the generated validator runs synchronously, so issues reported after an await are silently lost. Remove the async keyword.`);
|
|
3389
|
-
const argsObject = kind === "hooks.create" ? `{ input: _value, invoker: ${
|
|
3449
|
+
const argsObject = kind === "hooks.create" ? `{ input: _value, invoker: ${PRINCIPAL_VAR}, now: _now }` : kind === "hooks.update" ? `{ input: _value, oldValue: _oldValue, invoker: ${PRINCIPAL_VAR}, now: _now }` : kind === "validate" ? `{ value: _value }` : kind === "typeHook" ? `{ input: _input, oldRecord: _oldRecord, invoker: ${PRINCIPAL_VAR}, now: _now }` : `{ newRecord: _newRecord, oldRecord: _oldRecord, invoker: ${PRINCIPAL_VAR} }, __issues`;
|
|
3390
3450
|
const inlineExpr = assertParsableExpression(`(${fnSource})(${argsObject})`, context);
|
|
3391
3451
|
const freeVars = findUndefinedReferences(`const __fn = ${fnSource};`);
|
|
3392
3452
|
if (freeVars.size === 0) return inlineExpr;
|
|
@@ -3426,8 +3486,8 @@ async function bundleScriptTarget(args) {
|
|
|
3426
3486
|
* Precompile TailorDB hooks/validators into self-contained script expressions using rolldown.
|
|
3427
3487
|
* Uses oxc-parser AST walking to extract free variables from functions, then builds
|
|
3428
3488
|
* minimal entry points containing only the needed imports and declarations.
|
|
3429
|
-
* @param type - TailorDB
|
|
3430
|
-
* @param sourceFilePath - Source file where the
|
|
3489
|
+
* @param type - TailorDB table schema output.
|
|
3490
|
+
* @param sourceFilePath - Source file where the table is defined.
|
|
3431
3491
|
* @param tsconfig - Resolved tsconfig path, or undefined if not found.
|
|
3432
3492
|
*/
|
|
3433
3493
|
async function precompileTailorDBTypeScripts(type, sourceFilePath, tsconfig) {
|
|
@@ -3451,8 +3511,8 @@ async function precompileTailorDBTypeScripts(type, sourceFilePath, tsconfig) {
|
|
|
3451
3511
|
//#endregion
|
|
3452
3512
|
//#region src/cli/services/tailordb/type-name-validation.ts
|
|
3453
3513
|
/**
|
|
3454
|
-
* Format a TailorDB
|
|
3455
|
-
* @param sourceInfo - Source information captured when loading the
|
|
3514
|
+
* Format a TailorDB table source for validation errors.
|
|
3515
|
+
* @param sourceInfo - Source information captured when loading the table
|
|
3456
3516
|
* @returns Human-readable source detail
|
|
3457
3517
|
*/
|
|
3458
3518
|
function formatTailorDBTypeSourceInfo(sourceInfo) {
|
|
@@ -3467,9 +3527,9 @@ function formatTailorDBTypeSourceInfo(sourceInfo) {
|
|
|
3467
3527
|
return `${sourceInfo.filePath} export ${sourceInfo.exportName}`;
|
|
3468
3528
|
}
|
|
3469
3529
|
/**
|
|
3470
|
-
* Collect TailorDB
|
|
3530
|
+
* Collect TailorDB table-name sources from loaded local services.
|
|
3471
3531
|
* @param args - Collection inputs
|
|
3472
|
-
* @returns
|
|
3532
|
+
* @returns Table-name sources for local services
|
|
3473
3533
|
*/
|
|
3474
3534
|
function collectLocalTailorDBTypeNameSources(args) {
|
|
3475
3535
|
const sources = [];
|
|
@@ -3482,9 +3542,9 @@ function collectLocalTailorDBTypeNameSources(args) {
|
|
|
3482
3542
|
return sources;
|
|
3483
3543
|
}
|
|
3484
3544
|
/**
|
|
3485
|
-
* Fetch TailorDB
|
|
3545
|
+
* Fetch TailorDB table-name sources for external namespaces.
|
|
3486
3546
|
* @param args - Fetch inputs
|
|
3487
|
-
* @returns
|
|
3547
|
+
* @returns Table-name sources for external services
|
|
3488
3548
|
*/
|
|
3489
3549
|
async function fetchExternalTailorDBTypeNameSources(args) {
|
|
3490
3550
|
return (await Promise.all(args.externalTailorDBNamespaces.map(async (namespace) => {
|
|
@@ -3512,7 +3572,7 @@ async function fetchExternalTailorDBTypeNameSources(args) {
|
|
|
3512
3572
|
}))).flat();
|
|
3513
3573
|
}
|
|
3514
3574
|
/**
|
|
3515
|
-
* Assert that TailorDB
|
|
3575
|
+
* Assert that TailorDB table names are unique across all supplied sources.
|
|
3516
3576
|
* @param args - Validation inputs
|
|
3517
3577
|
*/
|
|
3518
3578
|
function assertUniqueTailorDBTypeNames(args) {
|
|
@@ -3526,20 +3586,20 @@ function assertUniqueTailorDBTypeNames(args) {
|
|
|
3526
3586
|
for (const [typeName, sources] of sourcesByTypeName) {
|
|
3527
3587
|
if (sources.length <= 1) continue;
|
|
3528
3588
|
const sourceList = sources.map(formatTailorDBTypeNameSource).join(", ");
|
|
3529
|
-
errors.push(`
|
|
3589
|
+
errors.push(`Table "${typeName}" is defined more than once: ${sourceList}`);
|
|
3530
3590
|
}
|
|
3531
|
-
if (errors.length > 0) throw new Error(`Duplicate TailorDB
|
|
3532
|
-
${errors.map((error) => ` - ${error}`).join("\n")}\nTailorDB
|
|
3591
|
+
if (errors.length > 0) throw new Error(`Duplicate TailorDB table names detected.
|
|
3592
|
+
${errors.map((error) => ` - ${error}`).join("\n")}\nTailorDB table names must be unique across all TailorDB namespaces in an application.`);
|
|
3533
3593
|
}
|
|
3534
3594
|
/**
|
|
3535
|
-
* Assert local TailorDB
|
|
3595
|
+
* Assert local TailorDB table names are unique.
|
|
3536
3596
|
* @param args - Validation inputs
|
|
3537
3597
|
*/
|
|
3538
3598
|
function assertUniqueLocalTailorDBTypeNames(args) {
|
|
3539
3599
|
assertUniqueTailorDBTypeNames({ sources: collectLocalTailorDBTypeNameSources(args) });
|
|
3540
3600
|
}
|
|
3541
3601
|
/**
|
|
3542
|
-
* Assert TailorDB
|
|
3602
|
+
* Assert TailorDB table names are unique across local and external namespaces.
|
|
3543
3603
|
* @param args - Validation inputs
|
|
3544
3604
|
*/
|
|
3545
3605
|
async function assertUniqueTailorDBTypeNamesWithExternal(args) {
|
|
@@ -3587,7 +3647,7 @@ function createTailorDBService(params) {
|
|
|
3587
3647
|
if (existingSourceInfo) {
|
|
3588
3648
|
const firstSource = formatTailorDBTypeSourceInfo(existingSourceInfo) ?? "unknown source";
|
|
3589
3649
|
const secondSource = formatTailorDBTypeSourceInfo(sourceInfo) ?? "unknown source";
|
|
3590
|
-
throw new Error(`Duplicate TailorDB
|
|
3650
|
+
throw new Error(`Duplicate TailorDB table name "${typeName}" detected in TailorDB service "${namespace}". First: ${firstSource}. Second: ${secondSource}. TailorDB table names must be unique across all TailorDB files in a service.`);
|
|
3591
3651
|
}
|
|
3592
3652
|
assertDefined(rawTypes[rawTypesKey], `raw types entry missing for key: ${rawTypesKey}`)[typeName] = type;
|
|
3593
3653
|
typeSourceInfo[typeName] = sourceInfo;
|
|
@@ -3600,7 +3660,7 @@ function createTailorDBService(params) {
|
|
|
3600
3660
|
const warnOmittedPermit = () => {
|
|
3601
3661
|
for (const fileTypes of Object.values(rawTypes)) for (const [typeName, type] of Object.entries(fileTypes)) {
|
|
3602
3662
|
const locations = findOmittedPermitRules(type.metadata.permissions);
|
|
3603
|
-
if (locations.length > 0) logger.warn(`TailorDB
|
|
3663
|
+
if (locations.length > 0) logger.warn(`TailorDB table "${typeName}" has permission rule(s) ${locations.join(", ")} in object form without an explicit "permit"; they default to "deny". Set permit: true (allow) or permit: false (deny) to silence this warning.`);
|
|
3604
3664
|
}
|
|
3605
3665
|
};
|
|
3606
3666
|
const validateRequiredPermissions = () => {
|
|
@@ -3611,16 +3671,16 @@ function createTailorDBService(params) {
|
|
|
3611
3671
|
if (!missingPermission && !missingGqlPermission) continue;
|
|
3612
3672
|
const source = formatTailorDBTypeSourceInfo(typeSourceInfo[typeName]);
|
|
3613
3673
|
const location = source ? ` (${source})` : "";
|
|
3614
|
-
if (missingPermission) errors.push(`TailorDB
|
|
3615
|
-
if (missingGqlPermission) errors.push(`TailorDB
|
|
3674
|
+
if (missingPermission) errors.push(`TailorDB table "${typeName}"${location} has no .permission() configured. TailorDB denies all record operations for tables without permission; call .permission(...) to grant access explicitly.`);
|
|
3675
|
+
if (missingGqlPermission) errors.push(`TailorDB table "${typeName}"${location} has no .gqlPermission() configured, but GraphQL operations are enabled for it. Call .gqlPermission(...) to grant GraphQL access explicitly, or disable GraphQL exposure with .features({ gqlOperations: { create: false, update: false, delete: false, read: false } }).`);
|
|
3616
3676
|
}
|
|
3617
3677
|
if (errors.length > 0) throw new Error(`TailorDB permission configuration errors in service "${namespace}":\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
3618
3678
|
};
|
|
3619
3679
|
/**
|
|
3620
|
-
* Process plugins for a
|
|
3621
|
-
* @param rawType - The raw TailorDB
|
|
3622
|
-
* @param attachments - Plugin attachments for this
|
|
3623
|
-
* @param sourceFilePath - The file path where the
|
|
3680
|
+
* Process plugins for a table and add generated tables to rawTypes
|
|
3681
|
+
* @param rawType - The raw TailorDB table being processed
|
|
3682
|
+
* @param attachments - Plugin attachments for this table
|
|
3683
|
+
* @param sourceFilePath - The file path where the table was loaded from
|
|
3624
3684
|
*/
|
|
3625
3685
|
const processPluginsForType = async (rawType, attachments, sourceFilePath) => {
|
|
3626
3686
|
if (!pluginManager) return;
|
|
@@ -3650,7 +3710,7 @@ function createTailorDBService(params) {
|
|
|
3650
3710
|
rawTypes[typeFile] = createRawTypesByName();
|
|
3651
3711
|
const loadedTypes = createRawTypesByName();
|
|
3652
3712
|
try {
|
|
3653
|
-
const module = await
|
|
3713
|
+
const module = await importUserModule(typeFile);
|
|
3654
3714
|
for (const exportName of Object.keys(module)) {
|
|
3655
3715
|
const exportedValue = module[exportName];
|
|
3656
3716
|
const result = TailorDBTypeSchema.safeParse(stripTailorDBTypeBuilderHelpers(exportedValue));
|
|
@@ -3666,15 +3726,16 @@ function createTailorDBService(params) {
|
|
|
3666
3726
|
filePath: typeFile,
|
|
3667
3727
|
exportName
|
|
3668
3728
|
});
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3729
|
+
const rawType = exportedValue;
|
|
3730
|
+
if (rawType.plugins && Array.isArray(rawType.plugins) && rawType.plugins.length > 0) {
|
|
3731
|
+
pluginAttachments.set(rawType.name, [...rawType.plugins]);
|
|
3732
|
+
logger.log(` Plugin attachments: ${styles.info(rawType.plugins.map((p) => p.pluginId).join(", "))}`);
|
|
3733
|
+
await processPluginsForType(rawType, rawType.plugins, typeFile);
|
|
3673
3734
|
}
|
|
3674
3735
|
}
|
|
3675
3736
|
} catch (error) {
|
|
3676
3737
|
const relativePath = path.relative(process.cwd(), typeFile);
|
|
3677
|
-
logger.error(`Failed to load
|
|
3738
|
+
logger.error(`Failed to load table from ${styles.bold(relativePath)}`);
|
|
3678
3739
|
logger.error(String(error));
|
|
3679
3740
|
throw error;
|
|
3680
3741
|
}
|
|
@@ -3698,7 +3759,7 @@ function createTailorDBService(params) {
|
|
|
3698
3759
|
const typeFiles = [...new Set(loadFilesWithIgnores(config, baseDir))];
|
|
3699
3760
|
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
3700
3761
|
logger.newline();
|
|
3701
|
-
logger.log(`Found ${styles.highlight(typeFiles.length.toString())}
|
|
3762
|
+
logger.log(`Found ${styles.highlight(typeFiles.length.toString())} table files for TailorDB service ${styles.highlight(`"${namespace}"`)}`);
|
|
3702
3763
|
if (pluginManager) for (const typeFile of typeFiles) await loadTypeFile(typeFile, tsconfig);
|
|
3703
3764
|
else await Promise.all(typeFiles.map((typeFile) => loadTypeFile(typeFile, tsconfig)));
|
|
3704
3765
|
doParseTypes();
|
|
@@ -4033,7 +4094,7 @@ function buildResolverPermissionGuardExpr(permission) {
|
|
|
4033
4094
|
}
|
|
4034
4095
|
/**
|
|
4035
4096
|
* Build the permission guard and input-validation statements shared by every
|
|
4036
|
-
* resolver entry wrapper (production bundling and `function
|
|
4097
|
+
* resolver entry wrapper (production bundling and `function run`).
|
|
4037
4098
|
*
|
|
4038
4099
|
* Kept as a single generator so a resolver-wrapping behavior (like the
|
|
4039
4100
|
* permission guard) can't be added to one entry-point template and forgotten
|
|
@@ -4207,8 +4268,8 @@ const HttpAdapterConfigSchema = z.strictObject({
|
|
|
4207
4268
|
|
|
4208
4269
|
//#endregion
|
|
4209
4270
|
//#region src/cli/services/http-adapter/bundler.ts
|
|
4210
|
-
const ADAPTER_BUNDLE_WARN_BYTES =
|
|
4211
|
-
const ADAPTER_BUNDLE_ERROR_BYTES =
|
|
4271
|
+
const ADAPTER_BUNDLE_WARN_BYTES = 65536;
|
|
4272
|
+
const ADAPTER_BUNDLE_ERROR_BYTES = 262144;
|
|
4212
4273
|
const GRAPHQL_WEB_MODULE = createRequire(import.meta.url).resolve("@0no-co/graphql.web");
|
|
4213
4274
|
/**
|
|
4214
4275
|
* Bundle each adapter's `input` (and `output`, if present) into a standalone
|
|
@@ -4229,12 +4290,13 @@ async function bundleHttpAdapters(adapters, baseDir, cache, bundleLogLevel = "DE
|
|
|
4229
4290
|
logger.newline();
|
|
4230
4291
|
logger.log(`Bundling ${styles.highlight(adapters.length.toString())} files for ${styles.info("\"http-adapter\"")}`);
|
|
4231
4292
|
const tsconfig = await resolveTSConfigWithFallback(baseDir);
|
|
4232
|
-
const
|
|
4293
|
+
const tasks = adapters.flatMap((adapter) => {
|
|
4233
4294
|
return (adapter.hasOutput ? ["input", "output"] : ["input"]).map((kind) => ({
|
|
4234
4295
|
adapter,
|
|
4235
4296
|
kind
|
|
4236
4297
|
}));
|
|
4237
|
-
})
|
|
4298
|
+
});
|
|
4299
|
+
const results = await withBundleConcurrency(tasks, ({ adapter, kind }) => bundleAdapterScript(adapter, kind, tsconfig, cache, bundleLogLevel, tsconfigCache));
|
|
4238
4300
|
const bundledInputs = /* @__PURE__ */ new Map();
|
|
4239
4301
|
const bundledOutputs = /* @__PURE__ */ new Map();
|
|
4240
4302
|
for (const [name, kind, code] of results) if (kind === "input") bundledInputs.set(name, code);
|
|
@@ -4437,7 +4499,7 @@ async function loadAdapterFiles(config, baseDir) {
|
|
|
4437
4499
|
}
|
|
4438
4500
|
async function loadAdapterFromFile(filePath) {
|
|
4439
4501
|
try {
|
|
4440
|
-
const module = await
|
|
4502
|
+
const module = await importUserModule(filePath);
|
|
4441
4503
|
if (!isSdkBranded(module.default, "http-adapter")) {
|
|
4442
4504
|
const named = Object.entries(module).find(([exportName, value]) => exportName !== "default" && isSdkBranded(value, "http-adapter"));
|
|
4443
4505
|
if (named) throw new Error(`HTTP adapter must be the default export, but it is exported as \`${named[0]}\`. Re-export it: \`export default createHttpAdapter({...})\`.`);
|
|
@@ -4533,7 +4595,7 @@ const ResolverSchema = z.strictObject({
|
|
|
4533
4595
|
* @returns Parsed resolver or null if invalid
|
|
4534
4596
|
*/
|
|
4535
4597
|
async function loadResolver(resolverFilePath) {
|
|
4536
|
-
const resolver = (await
|
|
4598
|
+
const resolver = (await importUserModule(resolverFilePath)).default;
|
|
4537
4599
|
const parseResult = ResolverSchema.safeParse(resolver);
|
|
4538
4600
|
if (!parseResult.success) return null;
|
|
4539
4601
|
return parseResult.data;
|
|
@@ -4609,16 +4671,17 @@ async function bundleSingleResolver(options) {
|
|
|
4609
4671
|
contextHash,
|
|
4610
4672
|
async build(cachePlugins, trackDependency) {
|
|
4611
4673
|
const absoluteSourcePath = path.resolve(resolver.sourceFile);
|
|
4674
|
+
const guardAndInputCheckExpr = buildResolverPermissionAndInputCheckExpr({
|
|
4675
|
+
permission: resolver.permission,
|
|
4676
|
+
defaultPermission
|
|
4677
|
+
});
|
|
4612
4678
|
const entryContent = multiline`
|
|
4613
4679
|
import _internalResolver from "${absoluteSourcePath}";
|
|
4614
4680
|
import { t } from "@tailor-platform/sdk";
|
|
4615
4681
|
|
|
4616
4682
|
const $tailor_resolver_body = async (context) => {
|
|
4617
4683
|
const invoker = ${INVOKER_EXPR};
|
|
4618
|
-
${
|
|
4619
|
-
permission: resolver.permission,
|
|
4620
|
-
defaultPermission
|
|
4621
|
-
})}
|
|
4684
|
+
${guardAndInputCheckExpr}
|
|
4622
4685
|
return _internalResolver.body({ ...context, invoker });
|
|
4623
4686
|
};
|
|
4624
4687
|
|
|
@@ -4673,7 +4736,7 @@ function parseResolverDefaultPermission(params) {
|
|
|
4673
4736
|
/**
|
|
4674
4737
|
* Find the `defaultPermission` of the resolver namespace a file belongs to.
|
|
4675
4738
|
*
|
|
4676
|
-
* `function
|
|
4739
|
+
* `function run` receives a single file rather than a namespace, so the
|
|
4677
4740
|
* namespace is recovered by matching the file against each namespace's own
|
|
4678
4741
|
* patterns — otherwise a test run would skip a guard the deployed resolver has.
|
|
4679
4742
|
*
|
|
@@ -4718,7 +4781,7 @@ function createResolverService(namespace, config, baseDir) {
|
|
|
4718
4781
|
});
|
|
4719
4782
|
const loadResolverForFile = async (resolverFile) => {
|
|
4720
4783
|
try {
|
|
4721
|
-
const resolverModule = await
|
|
4784
|
+
const resolverModule = await importUserModule(resolverFile);
|
|
4722
4785
|
const result = ResolverSchema.safeParse(resolverModule.default);
|
|
4723
4786
|
if (result.success) {
|
|
4724
4787
|
const relativePath = path.relative(process.cwd(), resolverFile);
|
|
@@ -5260,7 +5323,7 @@ async function loadFileContent(filePath) {
|
|
|
5260
5323
|
const jobs = [];
|
|
5261
5324
|
let workflow = null;
|
|
5262
5325
|
try {
|
|
5263
|
-
const module = await
|
|
5326
|
+
const module = await importUserModule(filePath);
|
|
5264
5327
|
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
5265
5328
|
if (exportName === "default") {
|
|
5266
5329
|
const workflowResult = WorkflowSchema.safeParse(stripRuntimeStart(exportValue));
|
|
@@ -5299,6 +5362,16 @@ async function loadFileContent(filePath) {
|
|
|
5299
5362
|
function getApplicationAuthNamespace(application) {
|
|
5300
5363
|
return application.authService?.config.name ?? application.config?.auth?.name;
|
|
5301
5364
|
}
|
|
5365
|
+
/**
|
|
5366
|
+
* Resolve the auth namespace configured for an application, throwing when none is configured.
|
|
5367
|
+
* @param application - Loaded application with local or external Auth config
|
|
5368
|
+
* @returns Auth namespace
|
|
5369
|
+
*/
|
|
5370
|
+
function requireApplicationAuthNamespace(application) {
|
|
5371
|
+
const authNamespace = getApplicationAuthNamespace(application);
|
|
5372
|
+
if (!authNamespace) throw new Error("No Auth service configured");
|
|
5373
|
+
return authNamespace;
|
|
5374
|
+
}
|
|
5302
5375
|
|
|
5303
5376
|
//#endregion
|
|
5304
5377
|
//#region src/cli/shared/inline-sourcemap.ts
|
|
@@ -5331,7 +5404,7 @@ const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
|
|
|
5331
5404
|
const AUTH_NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
5332
5405
|
const AIGatewaySchema = z.strictObject({
|
|
5333
5406
|
name: z.string().regex(NAME_PATTERN, "Must be 3-30 lowercase alphanumeric characters or hyphens").describe("AI Gateway name"),
|
|
5334
|
-
authNamespace: z.string().regex(AUTH_NAMESPACE_PATTERN, "Must be 3-63 lowercase alphanumeric characters or hyphens").describe("Auth namespace used to resolve request tokens against the workspace's auth"),
|
|
5407
|
+
authNamespace: z.string().regex(AUTH_NAMESPACE_PATTERN, "Must be 3-63 lowercase alphanumeric characters or hyphens").optional().describe("Auth namespace used to resolve request tokens against the workspace's auth. Defaults to the application's own auth service when omitted; omitting it without an Auth service configured is rejected."),
|
|
5335
5408
|
cors: z.array(z.string()).optional().describe("Allowed CORS origins for browser-based clients. Each entry is `*`, `http(s)://*`, `http(s)://*.example.com`, or `http(s)://app.example.com`, optionally with `:port`. Empty list disables cross-origin access.")
|
|
5336
5409
|
}).brand("AIGatewayConfig");
|
|
5337
5410
|
|
|
@@ -5530,6 +5603,65 @@ const StaticWebsiteSchema = z.strictObject({
|
|
|
5530
5603
|
customDomains: z.array(z.string()).optional().describe("Custom domains for the static website")
|
|
5531
5604
|
}).brand("StaticWebsiteConfig");
|
|
5532
5605
|
|
|
5606
|
+
//#endregion
|
|
5607
|
+
//#region src/parser/service/workflow/wait-point-key.ts
|
|
5608
|
+
const KEY_REGEX = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
5609
|
+
const LITERAL_SEGMENT_REGEX = /^[a-z0-9]+$/;
|
|
5610
|
+
const PARAM_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5611
|
+
const MAX_KEY_LENGTH = 63;
|
|
5612
|
+
const KEY_GRAMMAR = "[a-z0-9-] (3-63 characters; must start and end with [a-z0-9])";
|
|
5613
|
+
/**
|
|
5614
|
+
* Check one declared wait point key against the key rules.
|
|
5615
|
+
*
|
|
5616
|
+
* The declaration matters: only `createWaitPoints`' `define` receives the key
|
|
5617
|
+
* before the type arguments, so only there can TypeScript read the `$params`
|
|
5618
|
+
* off the key literal.
|
|
5619
|
+
* @param waitPoint - A key as declared, with the declaration it came from
|
|
5620
|
+
* @returns The rule the key breaks, or undefined when the key is usable
|
|
5621
|
+
*/
|
|
5622
|
+
function checkWaitPointKey(waitPoint) {
|
|
5623
|
+
const { key, declaredBy } = waitPoint;
|
|
5624
|
+
const segments = key.split("-");
|
|
5625
|
+
const paramSegments = segments.filter((segment) => segment.startsWith("$") && segment.length > 1);
|
|
5626
|
+
if (paramSegments.length > 0 && declaredBy !== "define") return declaredBy === "property" ? `Invalid wait point key "${key}": $params cannot come from a property name. Pass the key to define instead, e.g. define.for("${key}")<Payload, Result>().` : `Invalid wait point key "${key}": createWaitPoint takes its type arguments first, which stops TypeScript inferring the key as a literal, so it cannot type the $params. Declare it through createWaitPoints instead: createWaitPoints((define) => ({ myWaitPoint: define.for("${key}")<Payload, Result>() })).`;
|
|
5627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5628
|
+
let literals = 0;
|
|
5629
|
+
for (const segment of segments) {
|
|
5630
|
+
if (segment.startsWith("$")) {
|
|
5631
|
+
const name = segment.slice(1);
|
|
5632
|
+
if (!PARAM_NAME_REGEX.test(name)) return `Invalid wait point key "${key}": "${segment}" is not a usable parameter name. Use letters, digits and underscores, starting with a letter or underscore.`;
|
|
5633
|
+
if (seen.has(name)) return `Invalid wait point key "${key}": parameter "$${name}" appears more than once.`;
|
|
5634
|
+
seen.add(name);
|
|
5635
|
+
continue;
|
|
5636
|
+
}
|
|
5637
|
+
if (segment === "") continue;
|
|
5638
|
+
if (!LITERAL_SEGMENT_REGEX.test(segment)) return `Invalid wait point key "${key}": segment "${segment}" may only contain [a-z0-9]. Wait point keys accept ${KEY_GRAMMAR}, with $params standing in for runtime values.`;
|
|
5639
|
+
literals += 1;
|
|
5640
|
+
}
|
|
5641
|
+
if (literals === 0 && paramSegments.length > 0) return key.startsWith("-") || key.endsWith("-") ? `Invalid wait point key "${key}": must match ${KEY_GRAMMAR}.` : `Invalid wait point key "${key}": it needs at least one literal segment alongside its $params, otherwise the key carries no identity of its own and can collide with an unrelated wait point.`;
|
|
5642
|
+
if (paramSegments.length === 0) return KEY_REGEX.test(key) ? void 0 : `Invalid wait point key "${key}": must match ${KEY_GRAMMAR}.`;
|
|
5643
|
+
const shortest = segments.map((segment) => segment.startsWith("$") ? "0" : segment).join("-");
|
|
5644
|
+
if (shortest.length > MAX_KEY_LENGTH) return `Wait point key "${key}" cannot fit in ${MAX_KEY_LENGTH} characters: even single-character parameter values produce ${shortest.length}.`;
|
|
5645
|
+
return KEY_REGEX.test(shortest) ? void 0 : `Invalid wait point key "${key}": must match ${KEY_GRAMMAR}.`;
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* Check every declared wait point key, reporting each broken rule once.
|
|
5649
|
+
* @param waitPoints - The keys declared across the project, as registered
|
|
5650
|
+
* @returns One message per distinct declaration that breaks a rule
|
|
5651
|
+
*/
|
|
5652
|
+
function collectWaitPointKeyFailures(waitPoints) {
|
|
5653
|
+
const failures = [];
|
|
5654
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5655
|
+
for (const waitPoint of waitPoints) {
|
|
5656
|
+
const identity = `${waitPoint.declaredBy} ${waitPoint.key}`;
|
|
5657
|
+
if (seen.has(identity)) continue;
|
|
5658
|
+
seen.add(identity);
|
|
5659
|
+
const failure = checkWaitPointKey(waitPoint);
|
|
5660
|
+
if (failure) failures.push(failure);
|
|
5661
|
+
}
|
|
5662
|
+
return failures;
|
|
5663
|
+
}
|
|
5664
|
+
|
|
5533
5665
|
//#endregion
|
|
5534
5666
|
//#region src/cli/services/application.ts
|
|
5535
5667
|
function defineTailorDB(config, baseDir, pluginManager) {
|
|
@@ -5544,9 +5676,10 @@ function defineTailorDB(config, baseDir, pluginManager) {
|
|
|
5544
5676
|
for (const [namespace, serviceConfig] of Object.entries(config)) {
|
|
5545
5677
|
if ("external" in serviceConfig) externalTailorDBNamespaces.push(namespace);
|
|
5546
5678
|
else {
|
|
5679
|
+
const parsedConfig = TailorDBServiceConfigSchema.parse(serviceConfig);
|
|
5547
5680
|
const tailorDB = createTailorDBService({
|
|
5548
5681
|
namespace,
|
|
5549
|
-
config:
|
|
5682
|
+
config: parsedConfig,
|
|
5550
5683
|
pluginManager,
|
|
5551
5684
|
baseDir
|
|
5552
5685
|
});
|
|
@@ -5672,13 +5805,17 @@ function defineStaticWebsites(websites) {
|
|
|
5672
5805
|
});
|
|
5673
5806
|
return staticWebsiteServices;
|
|
5674
5807
|
}
|
|
5675
|
-
function defineAIGateways(gateways) {
|
|
5808
|
+
function defineAIGateways(gateways, applicationAuthNamespace) {
|
|
5676
5809
|
const aiGatewayServices = [];
|
|
5677
5810
|
const gatewayNames = /* @__PURE__ */ new Set();
|
|
5678
5811
|
(gateways ?? []).forEach((config) => {
|
|
5679
5812
|
const gateway = AIGatewaySchema.parse(config);
|
|
5680
5813
|
if (gatewayNames.has(gateway.name)) throw new Error(`AI Gateway with name "${gateway.name}" already defined.`);
|
|
5681
5814
|
gatewayNames.add(gateway.name);
|
|
5815
|
+
if (gateway.authNamespace === void 0) {
|
|
5816
|
+
if (!applicationAuthNamespace) throw new Error(`AI Gateway "${gateway.name}" has no "authNamespace" and no Auth service is configured to default to. Define an Auth service, or set "authNamespace" explicitly.`);
|
|
5817
|
+
gateway.authNamespace = applicationAuthNamespace;
|
|
5818
|
+
}
|
|
5682
5819
|
aiGatewayServices.push(gateway);
|
|
5683
5820
|
});
|
|
5684
5821
|
return aiGatewayServices;
|
|
@@ -5711,7 +5848,10 @@ function defineServices(config, baseDir, pluginManager) {
|
|
|
5711
5848
|
const idpResult = defineIdp(config.idp);
|
|
5712
5849
|
const authResult = defineAuth(config.auth, tailordbResult.tailorDBServices, tailordbResult.externalTailorDBNamespaces);
|
|
5713
5850
|
const staticWebsiteServices = defineStaticWebsites(config.staticWebsites);
|
|
5714
|
-
const aiGatewayServices = defineAIGateways(config.aiGateways
|
|
5851
|
+
const aiGatewayServices = defineAIGateways(config.aiGateways, getApplicationAuthNamespace({
|
|
5852
|
+
authService: authResult.authService,
|
|
5853
|
+
config
|
|
5854
|
+
}));
|
|
5715
5855
|
const { secrets, ignoreNullishValues } = parseSecretManager(config.secrets);
|
|
5716
5856
|
return {
|
|
5717
5857
|
tailordbResult,
|
|
@@ -5779,9 +5919,9 @@ function defineApplication(params) {
|
|
|
5779
5919
|
}
|
|
5780
5920
|
/**
|
|
5781
5921
|
* Generate plugin type and executor files if a plugin manager is provided.
|
|
5782
|
-
* Collects source
|
|
5922
|
+
* Collects source table info from TailorDB services and delegates to PluginManager.
|
|
5783
5923
|
* @param pluginManager - Plugin manager instance (skips if undefined)
|
|
5784
|
-
* @param tailorDBServices - TailorDB services to collect
|
|
5924
|
+
* @param tailorDBServices - TailorDB services to collect table source info from
|
|
5785
5925
|
* @param configPath - Path to tailor.config.ts for resolving plugin imports
|
|
5786
5926
|
* @returns Generated executor file paths
|
|
5787
5927
|
*/
|
|
@@ -5803,6 +5943,11 @@ function generatePluginFilesIfNeeded(pluginManager, tailorDBServices, configPath
|
|
|
5803
5943
|
executorGenerator: generatePluginExecutorFiles
|
|
5804
5944
|
});
|
|
5805
5945
|
}
|
|
5946
|
+
function assertWaitPointKeys() {
|
|
5947
|
+
const failures = collectWaitPointKeyFailures(getScopedWaitPoints());
|
|
5948
|
+
if (failures.length === 0) return;
|
|
5949
|
+
throw new Error(failures.join("\n"));
|
|
5950
|
+
}
|
|
5806
5951
|
/**
|
|
5807
5952
|
* Load and fully initialize a Tailor application.
|
|
5808
5953
|
* This performs all I/O-heavy operations: loading types, processing plugins,
|
|
@@ -5895,6 +6040,7 @@ async function loadApplication(params) {
|
|
|
5895
6040
|
await executorService.loadExecutors();
|
|
5896
6041
|
if (pluginExecutorFiles.length > 0) await executorService.loadPluginExecutorFiles([...pluginExecutorFiles]);
|
|
5897
6042
|
}
|
|
6043
|
+
assertWaitPointKeys();
|
|
5898
6044
|
if (workflowService) workflowService.printLoadedWorkflows();
|
|
5899
6045
|
if (httpAdapterService) httpAdapterService.printLoadedAdapters();
|
|
5900
6046
|
logger.newline();
|
|
@@ -5921,5 +6067,5 @@ async function loadApplication(params) {
|
|
|
5921
6067
|
}
|
|
5922
6068
|
|
|
5923
6069
|
//#endregion
|
|
5924
|
-
export {
|
|
5925
|
-
//# sourceMappingURL=application-
|
|
6070
|
+
export { rememberPlatformConfigForToken as $, getPluginGenerationDependencies as A, fetchMachineUserToken as B, createTsconfigPathsPlugin as C, createLogLevelTreeshakeOptions as D, composeFunctionTreeshakeOptions as E, hashFile as F, getOAuth2ClientId as G, fetchPlatformMachineUserToken as H, closeConnectionPool as I, initOAuth2Client as J, getOrNull as K, defaultPlatformBaseUrl as L, createBundleCache as M, getDistDir as N, resolveBundleLogLevel as O, hashContent as P, normalizeBaseUrl as Q, fetchAll as R, createGeneratedEntryResolverPlugin as S, platformBundleDefinePlugin as T, fetchUserInfo as U, fetchPaged as V, getConsoleBaseUrl as W, isDefaultPlatform as X, initOperatorClient as Y, isNotFoundError as Z, assertUniqueLocalTailorDBTypeNames as _, resolveInlineSourcemap as a, stringifyFunction as b, WorkflowJobFunctionExecutionPolicySchema as c, ResolverSchema as d, resolveStaticWebsiteUrls as et, HTTP_METHODS as f, buildResolverPermissionAndInputCheckExpr as g, buildResolverOperationHookExpr as h, resolverBundleKey as i, hasGenerationHooks as j, createBundleLog as k, WorkflowJobSchema as l, buildExecutorArgsExpr as m, generatePluginFilesIfNeeded as n, createApplyLimiter as nt, getApplicationAuthNamespace as o, INVOKER_EXPR as p, getPlatformBaseUrl as q, loadApplication as r, LOG_LEVELS as rt, requireApplicationAuthNamespace as s, defineApplication as t, byName as tt, resolveResolverDefaultPermissionForFile as u, assertUniqueTailorDBTypeNamesWithExternal as v, resolveTSConfigWithFallback as w, tailorPrincipalMap as x, PRINCIPAL_VAR as y, fetchAllTolerant as z };
|
|
6071
|
+
//# sourceMappingURL=application-C8a0UJ-z.mjs.map
|