@cedarjs/cli 9.0.0-canary.1784 → 9.0.0-canary.3124
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/README.md +7 -10
- package/dist/cfw.js +10 -6
- package/dist/commands/build/buildHandler.js +220 -45
- package/dist/commands/build/buildPackagesTask.js +21 -20
- package/dist/commands/build.js +13 -2
- package/dist/commands/check.js +4 -6
- package/dist/commands/console.js +6 -4
- package/dist/commands/deploy/baremetal/SshExecutor.js +13 -4
- package/dist/commands/deploy/baremetal/baremetalHandler.js +116 -50
- package/dist/commands/deploy/baremetal.js +11 -4
- package/dist/commands/deploy/flightcontrolHandler.js +28 -15
- package/dist/commands/deploy/helpers/deployHandler.js +11 -6
- package/dist/commands/deploy/netlify.js +1 -1
- package/dist/commands/deploy/packing/nft.js +1 -2
- package/dist/commands/deploy/render.js +1 -1
- package/dist/commands/deploy/renderHandler.js +24 -13
- package/dist/commands/deploy/serverlessHandler.js +69 -27
- package/dist/commands/deploy/vercel.js +1 -1
- package/dist/commands/destroy/component/component.js +2 -3
- package/dist/commands/destroy/directive/directive.js +1 -3
- package/dist/commands/destroy/directive/directiveHandler.js +2 -4
- package/dist/commands/destroy/function/function.js +1 -3
- package/dist/commands/destroy/handlerHelpers.js +17 -17
- package/dist/commands/destroy/helpers.js +8 -2
- package/dist/commands/destroy/layout/layout.js +1 -2
- package/dist/commands/destroy/page/pageHandler.js +3 -3
- package/dist/commands/destroy/scaffold/scaffoldHandler.js +31 -12
- package/dist/commands/destroy/sdl/sdlHandler.js +3 -3
- package/dist/commands/dev/apiDebugFlag.js +5 -4
- package/dist/commands/dev/devHandler.js +219 -68
- package/dist/commands/dev/packageWatchCommands.js +8 -3
- package/dist/commands/dev.js +16 -6
- package/dist/commands/execHandler.js +49 -29
- package/dist/commands/experimental/live-queries/liveQueries.js +32 -0
- package/dist/commands/experimental/live-queries/liveQueriesHandler.js +448 -0
- package/dist/commands/experimental/live-queries/templates/liveQueriesListener.ts.template +153 -0
- package/dist/commands/experimental/live-queries/templates/migration.sql.template +100 -0
- package/dist/commands/experimental/setupInngest.js +2 -2
- package/dist/commands/experimental/setupInngestHandler.js +13 -12
- package/dist/commands/experimental/setupOpentelemetry.js +2 -2
- package/dist/commands/experimental/setupOpentelemetryHandler.js +14 -10
- package/dist/commands/experimental/setupReactCompiler.js +2 -2
- package/dist/commands/experimental/setupReactCompilerHandler.js +48 -33
- package/dist/commands/experimental/setupRsc.js +1 -1
- package/dist/commands/experimental/setupRscHandler.js +20 -16
- package/dist/commands/experimental/setupStreamingSsr.js +2 -2
- package/dist/commands/experimental/setupStreamingSsrHandler.js +49 -34
- package/dist/commands/experimental/templates/opentelemetry.ts.template +2 -1
- package/dist/commands/experimental/templates/rsc/Document.tsx.template +1 -1
- package/dist/commands/experimental/templates/rsc/entry.client.tsx.template +8 -8
- package/dist/commands/experimental/templates/streamingSsr/Document.tsx.template +1 -1
- package/dist/commands/experimental/templates/streamingSsr/entry.client.tsx.template +6 -6
- package/dist/commands/experimental/templates/streamingSsr/tsconfig.json.template +6 -6
- package/dist/commands/experimental/util.js +27 -25
- package/dist/commands/experimental.js +3 -2
- package/dist/commands/generate/cell/cell.js +20 -0
- package/dist/commands/generate/cell/cellHandler.js +89 -40
- package/dist/commands/generate/cell/templates/cell.tsx.template +21 -2
- package/dist/commands/generate/cell/templates/cellFragment.tsx.template +43 -0
- package/dist/commands/generate/cell/templates/cellList.tsx.template +21 -2
- package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +23 -0
- package/dist/commands/generate/cell/templates/test.js.template +1 -1
- package/dist/commands/generate/cell/templates/testFragment.js.template +30 -0
- package/dist/commands/generate/component/componentHandler.js +14 -16
- package/dist/commands/generate/dataMigration/dataMigration.js +36 -18
- package/dist/commands/generate/dataMigration/templates/dataMigration.js.template +1 -1
- package/dist/commands/generate/dataMigration/templates/dataMigration.ts.template +1 -1
- package/dist/commands/generate/dbAuth/dbAuth.js +1 -0
- package/dist/commands/generate/dbAuth/dbAuthHandler.js +63 -34
- package/dist/commands/generate/dbAuth/templates/login.oauth.tsx.template +164 -0
- package/dist/commands/generate/dbAuth/templates/signup.oauth.tsx.template +147 -0
- package/dist/commands/generate/directive/directiveHandler.js +14 -16
- package/dist/commands/generate/function/function.js +1 -0
- package/dist/commands/generate/function/functionHandler.js +14 -19
- package/dist/commands/generate/helpers.js +34 -18
- package/dist/commands/generate/job/job.js +1 -0
- package/dist/commands/generate/job/jobHandler.js +20 -24
- package/dist/commands/generate/layout/layoutHandler.js +13 -16
- package/dist/commands/generate/model/model.js +1 -0
- package/dist/commands/generate/model/modelHandler.js +11 -5
- package/dist/commands/generate/ogImage/ogImage.js +1 -0
- package/dist/commands/generate/ogImage/ogImageHandler.js +18 -11
- package/dist/commands/generate/package/filesTask.js +9 -0
- package/dist/commands/generate/package/packageHandler.js +50 -61
- package/dist/commands/generate/package/templates/test.ts.template +2 -2
- package/dist/commands/generate/package/templates/vitest.config.ts.template +9 -0
- package/dist/commands/generate/page/pageHandler.js +27 -24
- package/dist/commands/generate/realtime/realtime.js +2 -2
- package/dist/commands/generate/realtime/realtimeHandler.js +51 -32
- package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +1 -1
- package/dist/commands/generate/scaffold/scaffold.js +1 -0
- package/dist/commands/generate/scaffold/scaffoldHandler.js +176 -49
- package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +1 -1
- package/dist/commands/generate/script/script.js +1 -0
- package/dist/commands/generate/script/scriptHandler.js +10 -6
- package/dist/commands/generate/script/templates/script.ts.template +9 -4
- package/dist/commands/generate/script/templates/tsconfig.json.template +16 -2
- package/dist/commands/generate/sdl/sdl.js +4 -3
- package/dist/commands/generate/sdl/sdlHandler.js +135 -46
- package/dist/commands/generate/sdl/stubFiles.js +132 -0
- package/dist/commands/generate/sdl/templates/sdl.js.template +2 -1
- package/dist/commands/generate/sdl/templates/sdl.ts.template +2 -1
- package/dist/commands/generate/secret/secret.js +20 -20
- package/dist/commands/generate/service/service.js +5 -5
- package/dist/commands/generate/service/serviceHandler.js +126 -61
- package/dist/commands/generate/service/templates/scenarios.ts.template +4 -2
- package/dist/commands/generate/service/templates/service.ts.template +22 -8
- package/dist/commands/generate/service/templates/test.ts.template +2 -2
- package/dist/commands/generate/yargsCommandHelpers.js +7 -2
- package/dist/commands/generate/yargsHandlerHelpers.js +20 -18
- package/dist/commands/generate.js +4 -4
- package/dist/commands/info.js +2 -2
- package/dist/commands/jobsHandler.js +7 -4
- package/dist/commands/lint.js +26 -69
- package/dist/commands/prerenderHandler.js +95 -58
- package/dist/commands/prismaHandler.js +51 -26
- package/dist/commands/record/init.js +2 -2
- package/dist/commands/record.js +4 -3
- package/dist/commands/serve.js +245 -31
- package/dist/commands/serveApiHandler.js +7 -6
- package/dist/commands/serveBothHandler.js +22 -13
- package/dist/commands/serveWebHandler.js +3 -2
- package/dist/commands/setup/auth/auth.js +15 -11
- package/dist/commands/setup/cache/cache.js +1 -1
- package/dist/commands/setup/cache/cacheHandler.js +15 -9
- package/dist/commands/setup/database/database.js +15 -0
- package/dist/commands/setup/database/postgres.js +19 -0
- package/dist/commands/setup/database/postgresHandler.js +196 -0
- package/dist/commands/setup/database/templates/db.ts.template +32 -0
- package/dist/commands/setup/deploy/deploy.js +4 -3
- package/dist/commands/setup/deploy/helpers/helpers.js +5 -1
- package/dist/commands/setup/deploy/helpers/index.js +132 -6
- package/dist/commands/setup/deploy/providers/baremetalHandler.js +28 -6
- package/dist/commands/setup/deploy/providers/coherence.js +1 -1
- package/dist/commands/setup/deploy/providers/coherenceHandler.js +60 -31
- package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +69 -51
- package/dist/commands/setup/deploy/providers/netlify.js +6 -0
- package/dist/commands/setup/deploy/providers/netlifyHandler.js +114 -13
- package/dist/commands/setup/deploy/providers/renderHandler.js +44 -41
- package/dist/commands/setup/deploy/providers/serverlessHandler.js +7 -5
- package/dist/commands/setup/deploy/providers/universal-deploy.js +11 -0
- package/dist/commands/setup/deploy/providers/universalDeployHandler.js +186 -0
- package/dist/commands/setup/deploy/providers/vercel.js +6 -0
- package/dist/commands/setup/deploy/providers/vercelHandler.js +115 -14
- package/dist/commands/setup/deploy/templates/baremetal.js +3 -2
- package/dist/commands/setup/deploy/templates/flightcontrol.js +60 -49
- package/dist/commands/setup/deploy/templates/netlify.js +2 -1
- package/dist/commands/setup/deploy/templates/netlifyUD.js +31 -0
- package/dist/commands/setup/deploy/templates/render.js +32 -17
- package/dist/commands/setup/docker/docker.js +2 -2
- package/dist/commands/setup/docker/dockerHandler.js +74 -39
- package/dist/commands/setup/docker/templates/Dockerfile.npm +119 -0
- package/dist/commands/setup/docker/templates/Dockerfile.pnpm +112 -0
- package/dist/commands/setup/docker/templates/{Dockerfile → Dockerfile.yarn} +4 -7
- package/dist/commands/setup/docker/templates/docker-compose.dev.yml +3 -3
- package/dist/commands/setup/docker/templates/docker-compose.prod.yml +2 -5
- package/dist/commands/setup/docker/templates/dockerignore +1 -0
- package/dist/commands/setup/generator/generator.js +1 -1
- package/dist/commands/setup/generator/generatorHandler.js +5 -4
- package/dist/commands/setup/graphql/features/fragments/appGqlConfigTransform.js +6 -3
- package/dist/commands/setup/graphql/features/fragments/fragments.js +1 -1
- package/dist/commands/setup/graphql/features/fragments/fragmentsHandler.js +5 -7
- package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocuments.js +1 -1
- package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocumentsHandler.js +4 -3
- package/dist/commands/setup/i18n/i18n.js +1 -1
- package/dist/commands/setup/i18n/i18nHandler.js +21 -17
- package/dist/commands/setup/jobs/jobs.js +1 -1
- package/dist/commands/setup/jobs/jobsHandler.js +17 -12
- package/dist/commands/setup/mailer/mailer.js +1 -1
- package/dist/commands/setup/mailer/mailerHandler.js +13 -11
- package/dist/commands/setup/middleware/ogImage/ogImage.js +1 -1
- package/dist/commands/setup/middleware/ogImage/ogImageHandler.js +2 -1
- package/dist/commands/setup/monitoring/sentry/sentry.js +1 -1
- package/dist/commands/setup/monitoring/sentry/sentryHandler.js +18 -12
- package/dist/commands/setup/neon/neon.js +35 -0
- package/dist/commands/setup/neon/neonHandler.js +264 -0
- package/dist/commands/setup/package/package.js +1 -1
- package/dist/commands/setup/package/packageHandler.js +18 -14
- package/dist/commands/setup/realtime/addRealtimeToGraphql.js +1 -1
- package/dist/commands/setup/realtime/realtime.js +1 -1
- package/dist/commands/setup/realtime/realtimeHandler.js +26 -37
- package/dist/commands/setup/realtime/templates/realtime.ts.template +1 -1
- package/dist/commands/setup/realtime/templates/subscriptions/countdown/countdown.ts.template +1 -1
- package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
- package/dist/commands/setup/server-file/serverFile.js +1 -1
- package/dist/commands/setup/server-file/serverFileHandler.js +18 -10
- package/dist/commands/setup/tenancy/authCodemod.js +66 -0
- package/dist/commands/setup/tenancy/dbCodemod.js +64 -0
- package/dist/commands/setup/tenancy/graphqlCodemod.js +88 -0
- package/dist/commands/setup/tenancy/preflight.js +49 -0
- package/dist/commands/setup/tenancy/schemaPrisma.js +81 -0
- package/dist/commands/setup/tenancy/signupCodemod.js +153 -0
- package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +63 -0
- package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +40 -0
- package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +39 -0
- package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +59 -0
- package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +89 -0
- package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +389 -0
- package/dist/commands/setup/tenancy/templates/organizations.ts.template +394 -0
- package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +35 -0
- package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +25 -0
- package/dist/commands/setup/tenancy/tenancy.js +30 -0
- package/dist/commands/setup/tenancy/tenancyHandler.js +425 -0
- package/dist/commands/setup/ui/helpers/helpers.js +5 -1
- package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +16 -8
- package/dist/commands/setup/ui/libraries/mantineHandler.js +26 -16
- package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +40 -29
- package/dist/commands/setup/uploads/schemaPrisma.js +41 -0
- package/dist/commands/setup/uploads/serverFile.js +151 -0
- package/dist/commands/setup/uploads/templates/requireUploadToken.ts.template +15 -0
- package/dist/commands/setup/uploads/templates/srcLibUploads.ts.template +23 -24
- package/dist/commands/setup/uploads/templates/uploads.sdl.ts.template +79 -0
- package/dist/commands/setup/uploads/templates/uploads.ts.template +89 -0
- package/dist/commands/setup/uploads/templates/withDataUri.ts.template +14 -0
- package/dist/commands/setup/uploads/templates/withSignedUrl.ts.template +16 -0
- package/dist/commands/setup/uploads/uploads.js +49 -7
- package/dist/commands/setup/uploads/uploadsHandler.js +266 -72
- package/dist/commands/setup.js +5 -4
- package/dist/commands/studio.js +1 -1
- package/dist/commands/studioHandler.js +2 -2
- package/dist/commands/test/datasourceWarning.js +42 -0
- package/dist/commands/test/testHandler.js +104 -0
- package/dist/commands/test/viteVersionCheck.js +75 -0
- package/dist/commands/test.js +16 -19
- package/dist/commands/type-check.js +2 -1
- package/dist/commands/type-checkHandler.js +25 -18
- package/dist/commands/upgrade/preUpgradeScripts.js +11 -11
- package/dist/commands/upgrade/upgrade.js +4 -2
- package/dist/commands/upgrade/upgradeHandler.js +97 -73
- package/dist/index.js +15 -11
- package/dist/lib/background.js +22 -1
- package/dist/lib/configureStorybook.js +2 -2
- package/dist/lib/exec.js +68 -63
- package/dist/lib/exit.js +20 -5
- package/dist/lib/extendFile.js +23 -13
- package/dist/lib/generatePrismaClient.js +48 -12
- package/dist/lib/index.js +137 -106
- package/dist/lib/loadEnvFiles.js +1 -1
- package/dist/lib/locking.js +8 -4
- package/dist/lib/merge/algorithms.js +4 -1
- package/dist/lib/merge/index.js +78 -41
- package/dist/lib/merge/semanticIdentity.js +18 -6
- package/dist/lib/merge/strategy.js +86 -36
- package/dist/lib/packages.js +23 -13
- package/dist/lib/plugin.js +13 -7
- package/dist/lib/pluralHelpers.js +6 -2
- package/dist/lib/project.js +3 -1
- package/dist/lib/rollback.js +7 -7
- package/dist/lib/schemaHelpers.js +16 -19
- package/dist/lib/test.js +20 -5
- package/dist/lib/updateCheck.js +47 -18
- package/dist/middleware/checkNodeVersion.js +9 -4
- package/dist/middleware/{detectProjectRxVersion.js → detectProjectCedarVersion.js} +2 -2
- package/dist/plugin.js +18 -12
- package/dist/telemetry/exporter.js +12 -5
- package/dist/telemetry/index.js +4 -3
- package/dist/telemetry/resource.js +9 -12
- package/dist/telemetry/send.js +14 -10
- package/dist/testLib/cells.js +5 -3
- package/package.json +44 -41
- package/LICENSE +0 -21
- package/dist/commands/consoleHandler.js +0 -68
- package/dist/commands/setup/tsconfig/tsconfig.js +0 -25
- package/dist/commands/setup/tsconfig/tsconfigHandler.js +0 -59
- package/dist/commands/setup/uploads/dbCodemod.js +0 -28
- package/dist/commands/setup/uploads/templates/signedUrl.ts.template +0 -21
- package/dist/commands/setup/vite/templates/vite.config.ts.template +0 -19
- package/dist/commands/setup/vite/vite.js +0 -38
- package/dist/commands/setup/vite/viteHandler.js +0 -83
- package/dist/commands/testEsm.js +0 -36
- package/dist/commands/testHandler.js +0 -137
- package/dist/commands/testHandlerEsm.js +0 -85
- package/dist/commands/ts-to-js.js +0 -20
- package/dist/lib/cedarPluralize.js +0 -56
- package/dist/lib/colors.js +0 -18
- package/dist/testLib/getFilesWithPattern.js +0 -21
- /package/dist/{index.d.js → global.d.js} +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Listr } from "listr2";
|
|
4
|
+
import { colors as c } from "@cedarjs/cli-helpers/colors";
|
|
5
|
+
import {
|
|
6
|
+
addApiPackages,
|
|
7
|
+
addWebPackages
|
|
8
|
+
} from "@cedarjs/cli-helpers/installHelpers";
|
|
9
|
+
import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
|
|
10
|
+
import {
|
|
11
|
+
getConfigPath,
|
|
12
|
+
getMigrationsPath,
|
|
13
|
+
getSchemaPath
|
|
14
|
+
} from "@cedarjs/project-config";
|
|
15
|
+
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
16
|
+
import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
|
|
17
|
+
import { isTypeScriptProject } from "../../../lib/project.js";
|
|
18
|
+
function getApiPackageJson() {
|
|
19
|
+
const apiPackageJsonPath = path.join(getPaths().api.base, "package.json");
|
|
20
|
+
return JSON.parse(fs.readFileSync(apiPackageJsonPath, "utf-8"));
|
|
21
|
+
}
|
|
22
|
+
function getWebPackageJson() {
|
|
23
|
+
const webPackageJsonPath = path.join(getPaths().web.base, "package.json");
|
|
24
|
+
return JSON.parse(fs.readFileSync(webPackageJsonPath, "utf-8"));
|
|
25
|
+
}
|
|
26
|
+
function hasPackage(packageJson, packageName) {
|
|
27
|
+
return Boolean(
|
|
28
|
+
packageJson.dependencies?.[packageName] || packageJson.devDependencies?.[packageName]
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
async function getPrismaProvider() {
|
|
32
|
+
try {
|
|
33
|
+
const prismaConfigPath = getPaths().api.prismaConfig;
|
|
34
|
+
if (!prismaConfigPath) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
let schemaPath = await getSchemaPath(prismaConfigPath);
|
|
38
|
+
if (!schemaPath) {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
let stat;
|
|
42
|
+
try {
|
|
43
|
+
stat = fs.statSync(schemaPath);
|
|
44
|
+
} catch {
|
|
45
|
+
stat = void 0;
|
|
46
|
+
}
|
|
47
|
+
if (stat?.isDirectory()) {
|
|
48
|
+
const candidate = path.join(schemaPath, "schema.prisma");
|
|
49
|
+
if (fs.existsSync(candidate)) {
|
|
50
|
+
schemaPath = candidate;
|
|
51
|
+
} else {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!fs.existsSync(schemaPath)) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const content = fs.readFileSync(schemaPath, "utf-8");
|
|
59
|
+
const match = content.match(/^\s*provider\s*=\s*["']([^"']+)["']/im);
|
|
60
|
+
if (match?.[1]) {
|
|
61
|
+
return match[1].toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
return void 0;
|
|
64
|
+
} catch {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function findExistingLiveQueryMigration(migrationsDirectoryPath) {
|
|
69
|
+
if (!fs.existsSync(migrationsDirectoryPath)) {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
const globPattern = path.join(migrationsDirectoryPath, "*", "migration.sql").replaceAll("\\", "/");
|
|
73
|
+
const migrationFilePaths = fs.globSync(globPattern);
|
|
74
|
+
return migrationFilePaths.find((migrationFilePath) => {
|
|
75
|
+
const content = fs.readFileSync(migrationFilePath, "utf-8");
|
|
76
|
+
return content.includes("cedar_notify_table_change");
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function generateMigrationFolderName() {
|
|
80
|
+
const now = /* @__PURE__ */ new Date();
|
|
81
|
+
const year = String(now.getFullYear());
|
|
82
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
83
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
84
|
+
const hour = String(now.getHours()).padStart(2, "0");
|
|
85
|
+
const minute = String(now.getMinutes()).padStart(2, "0");
|
|
86
|
+
const second = String(now.getSeconds()).padStart(2, "0");
|
|
87
|
+
return `${year}${month}${day}${hour}${minute}${second}_live_queries_notifications`;
|
|
88
|
+
}
|
|
89
|
+
function addLiveQueryListenerToGraphqlHandler({ force }) {
|
|
90
|
+
const graphqlHandlerPath = path.join(
|
|
91
|
+
getPaths().api.functions,
|
|
92
|
+
`graphql.${isTypeScriptProject() ? "ts" : "js"}`
|
|
93
|
+
);
|
|
94
|
+
if (!fs.existsSync(graphqlHandlerPath)) {
|
|
95
|
+
return {
|
|
96
|
+
skipped: true,
|
|
97
|
+
reason: "GraphQL handler not found"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const contentLines = fs.readFileSync(graphqlHandlerPath, "utf-8").split("\n");
|
|
101
|
+
const importLineRegex = /^import {.*startLiveQueryListener.*} from ['"]src\/lib\/liveQueriesListener['"];?$/;
|
|
102
|
+
const multilineImportRegex = /^} from ['"]src\/lib\/liveQueriesListener['"];?$/;
|
|
103
|
+
const hasImport = contentLines.some((line) => {
|
|
104
|
+
return importLineRegex.test(line) || multilineImportRegex.test(line);
|
|
105
|
+
});
|
|
106
|
+
const hasStartCall = contentLines.some(
|
|
107
|
+
(line) => line.trim().startsWith("startLiveQueryListener(") || line.trim().startsWith("void startLiveQueryListener(")
|
|
108
|
+
);
|
|
109
|
+
if (hasImport && hasStartCall && !force) {
|
|
110
|
+
return {
|
|
111
|
+
skipped: true,
|
|
112
|
+
reason: "Listener is already wired into GraphQL handler"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const handlerIndex = contentLines.findLastIndex(
|
|
116
|
+
(line) => line === "export const handler = createGraphQLHandler({"
|
|
117
|
+
);
|
|
118
|
+
if (handlerIndex === -1) {
|
|
119
|
+
return {
|
|
120
|
+
skipped: true,
|
|
121
|
+
reason: "Unexpected syntax. Handler not found"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const lastImportIndex = contentLines.slice(0, handlerIndex).findLastIndex((line) => line.startsWith("import "));
|
|
125
|
+
if (lastImportIndex === -1) {
|
|
126
|
+
return {
|
|
127
|
+
skipped: true,
|
|
128
|
+
reason: "Unexpected syntax. No imports found"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (!hasImport) {
|
|
132
|
+
const loggerImportIndex = contentLines.findIndex(
|
|
133
|
+
(line) => /import { logger } from ['"]src\/lib\/logger['"]/.test(line)
|
|
134
|
+
);
|
|
135
|
+
const insertIndex = loggerImportIndex >= 0 ? loggerImportIndex : lastImportIndex + 1;
|
|
136
|
+
contentLines.splice(
|
|
137
|
+
insertIndex,
|
|
138
|
+
0,
|
|
139
|
+
"import { startLiveQueryListener } from 'src/lib/liveQueriesListener'"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const handlerIndexAfterImport = hasImport ? handlerIndex : handlerIndex + 1;
|
|
143
|
+
if (!hasStartCall) {
|
|
144
|
+
contentLines.splice(
|
|
145
|
+
handlerIndexAfterImport,
|
|
146
|
+
0,
|
|
147
|
+
"// Fire-and-forget: we intentionally don't await this so it doesn't block the",
|
|
148
|
+
"// GraphQL handler from being registered. The listener doesn't need to be ready",
|
|
149
|
+
"// before the first request is handled.",
|
|
150
|
+
"void startLiveQueryListener()",
|
|
151
|
+
""
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
fs.writeFileSync(graphqlHandlerPath, contentLines.join("\n"));
|
|
155
|
+
return {
|
|
156
|
+
skipped: false
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function addConfigureGqlormToApp({ force }) {
|
|
160
|
+
const appPath = getPaths().web.app;
|
|
161
|
+
if (!fs.existsSync(appPath)) {
|
|
162
|
+
return {
|
|
163
|
+
skipped: true,
|
|
164
|
+
reason: `${path.basename(appPath)} not found`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const content = fs.readFileSync(appPath, "utf-8");
|
|
168
|
+
const contentLines = content.split("\n");
|
|
169
|
+
const hasGqlormImport = contentLines.some(
|
|
170
|
+
(line) => line.includes("from '@cedarjs/gqlorm/setup'")
|
|
171
|
+
);
|
|
172
|
+
const hasSchemaImport = contentLines.some(
|
|
173
|
+
(line) => line.includes("from '../../.cedar/gqlorm-schema.json'")
|
|
174
|
+
);
|
|
175
|
+
const hasConfigureCall = contentLines.some(
|
|
176
|
+
(line) => line.includes("configureGqlorm({ schema })")
|
|
177
|
+
);
|
|
178
|
+
if (hasGqlormImport && hasSchemaImport && hasConfigureCall && !force) {
|
|
179
|
+
return {
|
|
180
|
+
skipped: true,
|
|
181
|
+
reason: "configureGqlorm is already wired into App"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (!hasGqlormImport) {
|
|
185
|
+
const redwoodWebImportIndex = contentLines.findIndex(
|
|
186
|
+
(line) => line.includes("from '@cedarjs/web'")
|
|
187
|
+
);
|
|
188
|
+
if (redwoodWebImportIndex === -1) {
|
|
189
|
+
return {
|
|
190
|
+
skipped: true,
|
|
191
|
+
reason: "Unexpected syntax. Could not find @cedarjs/web import to insert gqlorm import"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
contentLines.splice(
|
|
195
|
+
redwoodWebImportIndex,
|
|
196
|
+
0,
|
|
197
|
+
"import { configureGqlorm } from '@cedarjs/gqlorm/setup'"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (!hasSchemaImport) {
|
|
201
|
+
const fatalErrorPageIndex = contentLines.findIndex(
|
|
202
|
+
(line) => line.includes("import FatalErrorPage from 'src/pages/FatalErrorPage'")
|
|
203
|
+
);
|
|
204
|
+
if (fatalErrorPageIndex === -1) {
|
|
205
|
+
return {
|
|
206
|
+
skipped: true,
|
|
207
|
+
reason: "Unexpected syntax. Could not find FatalErrorPage import to insert schema import"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
contentLines.splice(
|
|
211
|
+
fatalErrorPageIndex + 1,
|
|
212
|
+
0,
|
|
213
|
+
"import schema from '../../.cedar/gqlorm-schema.json' with { type: 'json' }"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (!hasConfigureCall) {
|
|
217
|
+
const appComponentIndex = contentLines.findIndex(
|
|
218
|
+
(line) => /^const App\s*=/.test(line)
|
|
219
|
+
);
|
|
220
|
+
if (appComponentIndex === -1) {
|
|
221
|
+
return {
|
|
222
|
+
skipped: true,
|
|
223
|
+
reason: "Unexpected syntax. Could not find `const App =` to insert configureGqlorm call"
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
contentLines.splice(appComponentIndex, 0, "configureGqlorm({ schema })", "");
|
|
227
|
+
}
|
|
228
|
+
fs.writeFileSync(appPath, contentLines.join("\n"));
|
|
229
|
+
return {
|
|
230
|
+
skipped: false
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async function handler({ force, verbose }) {
|
|
234
|
+
const projectIsTypescript = isTypeScriptProject();
|
|
235
|
+
const apiPackageJson = getApiPackageJson();
|
|
236
|
+
const migrationsPath = await getMigrationsPath(getPaths().api.prismaConfig);
|
|
237
|
+
const hasRealtimeDependency = hasPackage(apiPackageJson, "@cedarjs/realtime");
|
|
238
|
+
const hasPgDependency = hasPackage(apiPackageJson, "pg");
|
|
239
|
+
const webPackageJson = getWebPackageJson();
|
|
240
|
+
const hasGqlormDependency = hasPackage(webPackageJson, "@cedarjs/gqlorm");
|
|
241
|
+
const ext = projectIsTypescript ? "ts" : "js";
|
|
242
|
+
const migrationTemplatePath = path.resolve(
|
|
243
|
+
import.meta.dirname,
|
|
244
|
+
"templates",
|
|
245
|
+
"migration.sql.template"
|
|
246
|
+
);
|
|
247
|
+
const listenerTemplatePath = path.resolve(
|
|
248
|
+
import.meta.dirname,
|
|
249
|
+
"templates",
|
|
250
|
+
"liveQueriesListener.ts.template"
|
|
251
|
+
);
|
|
252
|
+
const existingMigrationPath = findExistingLiveQueryMigration(migrationsPath);
|
|
253
|
+
const migrationDirPath = path.join(
|
|
254
|
+
migrationsPath,
|
|
255
|
+
generateMigrationFolderName()
|
|
256
|
+
);
|
|
257
|
+
const migrationPath = path.join(migrationDirPath, "migration.sql");
|
|
258
|
+
const listenerPath = path.join(
|
|
259
|
+
getPaths().api.lib,
|
|
260
|
+
`liveQueriesListener.${ext}`
|
|
261
|
+
);
|
|
262
|
+
const tasks = new Listr(
|
|
263
|
+
[
|
|
264
|
+
{
|
|
265
|
+
title: "Checking for @cedarjs/realtime in api workspace...",
|
|
266
|
+
task: () => {
|
|
267
|
+
if (!hasRealtimeDependency) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`@cedarjs/realtime is not installed in your api workspace. Please run ${c.highlight(formatCedarCommand(["setup", "realtime"]))} first.`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
title: "Checking that your database provider is PostgreSQL...",
|
|
276
|
+
task: async () => {
|
|
277
|
+
const prismaProvider = await getPrismaProvider();
|
|
278
|
+
const unsupportedProviders = /* @__PURE__ */ new Set([
|
|
279
|
+
"sqlite",
|
|
280
|
+
"mysql",
|
|
281
|
+
"mongodb",
|
|
282
|
+
"sqlserver",
|
|
283
|
+
"cockroachdb"
|
|
284
|
+
]);
|
|
285
|
+
if (prismaProvider && unsupportedProviders.has(prismaProvider)) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Only PostgreSQL is supported for now (found provider "${prismaProvider}").`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
title: "Adding [experimental.gqlorm] config...",
|
|
294
|
+
task: (_ctx, task) => {
|
|
295
|
+
const configTomlPath = getConfigPath();
|
|
296
|
+
const configFileName = path.basename(configTomlPath);
|
|
297
|
+
const configContent = fs.readFileSync(configTomlPath, "utf-8");
|
|
298
|
+
if (!configContent.includes("[experimental.gqlorm]")) {
|
|
299
|
+
writeFile(
|
|
300
|
+
configTomlPath,
|
|
301
|
+
configContent.concat(
|
|
302
|
+
"\n[experimental.gqlorm]\n enabled = true\n"
|
|
303
|
+
),
|
|
304
|
+
{
|
|
305
|
+
overwriteExisting: true
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
if (force) {
|
|
310
|
+
task.output = `Overwriting config in ${configFileName}`;
|
|
311
|
+
writeFile(
|
|
312
|
+
configTomlPath,
|
|
313
|
+
configContent.replace(
|
|
314
|
+
"\n[experimental.gqlorm]\n enabled = false\n",
|
|
315
|
+
"\n[experimental.gqlorm]\n enabled = true\n"
|
|
316
|
+
),
|
|
317
|
+
{
|
|
318
|
+
overwriteExisting: true
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
} else {
|
|
322
|
+
task.skip(
|
|
323
|
+
`The [experimental.gqlorm] config block already exists in ${configFileName}.`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
rendererOptions: { persistentOutput: true }
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
...addApiPackages(["pg@^8.18.0"]),
|
|
332
|
+
title: "Adding pg dependency to your api workspace...",
|
|
333
|
+
skip: () => {
|
|
334
|
+
if (hasPgDependency) {
|
|
335
|
+
return "pg is already installed";
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
title: "Adding live query notification migration...",
|
|
342
|
+
task: () => {
|
|
343
|
+
const migrationTemplate = fs.readFileSync(
|
|
344
|
+
migrationTemplatePath,
|
|
345
|
+
"utf-8"
|
|
346
|
+
);
|
|
347
|
+
const targetPath = force && existingMigrationPath ? existingMigrationPath : migrationPath;
|
|
348
|
+
writeFile(targetPath, migrationTemplate, {
|
|
349
|
+
overwriteExisting: force
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
skip: () => {
|
|
353
|
+
if (existingMigrationPath && !force) {
|
|
354
|
+
const migrationPath2 = path.relative(
|
|
355
|
+
getPaths().base,
|
|
356
|
+
existingMigrationPath
|
|
357
|
+
);
|
|
358
|
+
return `Existing live query migration found: ${migrationPath2}`;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
title: `Adding api/src/lib/liveQueriesListener.${ext}...`,
|
|
365
|
+
task: async () => {
|
|
366
|
+
const listenerTemplate = fs.readFileSync(
|
|
367
|
+
listenerTemplatePath,
|
|
368
|
+
"utf-8"
|
|
369
|
+
);
|
|
370
|
+
const listenerContent = projectIsTypescript ? listenerTemplate : await transformTSToJS(listenerPath, listenerTemplate);
|
|
371
|
+
writeFile(listenerPath, listenerContent, {
|
|
372
|
+
overwriteExisting: force
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
title: "Wiring listener startup into GraphQL handler...",
|
|
378
|
+
task: (_ctx, task) => {
|
|
379
|
+
const result = addLiveQueryListenerToGraphqlHandler({ force });
|
|
380
|
+
if (result.skipped) {
|
|
381
|
+
task.skip(result.reason);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
...addWebPackages(["@cedarjs/gqlorm"]),
|
|
387
|
+
title: "Adding @cedarjs/gqlorm to your web workspace...",
|
|
388
|
+
skip: () => {
|
|
389
|
+
if (hasGqlormDependency) {
|
|
390
|
+
return "@cedarjs/gqlorm is already installed";
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
title: "Wiring configureGqlorm into App.tsx...",
|
|
397
|
+
task: (_ctx, task) => {
|
|
398
|
+
const result = addConfigureGqlormToApp({ force });
|
|
399
|
+
if (result.skipped) {
|
|
400
|
+
task.skip(result.reason);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
title: "One more thing...",
|
|
406
|
+
task: (_ctx, task) => {
|
|
407
|
+
task.title = `One more thing...
|
|
408
|
+
|
|
409
|
+
${c.success("\nLive query notifications configured!\n")}
|
|
410
|
+
|
|
411
|
+
Apply the migration to activate Postgres notifications:
|
|
412
|
+
${c.highlight(`
|
|
413
|
+
\xA0\xA0${formatCedarCommand(["prisma", "migrate", "dev"])}
|
|
414
|
+
`)}
|
|
415
|
+
|
|
416
|
+
You're then ready to use @live queries to get real-time updates as
|
|
417
|
+
soon as something in your database changes.
|
|
418
|
+
`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
],
|
|
422
|
+
{
|
|
423
|
+
rendererOptions: { collapseSubtasks: false },
|
|
424
|
+
renderer: verbose ? "verbose" : "default"
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
try {
|
|
428
|
+
await tasks.run();
|
|
429
|
+
} catch (e) {
|
|
430
|
+
if (isObject(e) && "message" in e) {
|
|
431
|
+
errorTelemetry(process.argv, e.message);
|
|
432
|
+
console.error(c.error(e.message));
|
|
433
|
+
} else {
|
|
434
|
+
errorTelemetry(process.argv, e);
|
|
435
|
+
console.error(c.error(e));
|
|
436
|
+
}
|
|
437
|
+
process.exit(isObjectWithExitCode(e) ? e.exitCode : 1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function isObject(value) {
|
|
441
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
442
|
+
}
|
|
443
|
+
function isObjectWithExitCode(value) {
|
|
444
|
+
return isObject(value) && typeof value.exitCode === "number";
|
|
445
|
+
}
|
|
446
|
+
export {
|
|
447
|
+
handler
|
|
448
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Client } from 'pg'
|
|
2
|
+
|
|
3
|
+
import { getPaths, loadPrismaConfig } from '@cedarjs/project-config'
|
|
4
|
+
import { liveQueryStore } from '@cedarjs/realtime'
|
|
5
|
+
import { pluralize } from '@cedarjs/utils/cedarPluralize'
|
|
6
|
+
|
|
7
|
+
import { logger } from 'src/lib/logger'
|
|
8
|
+
|
|
9
|
+
const LIVE_QUERY_CHANNEL = 'table_change'
|
|
10
|
+
const RECONNECT_DELAY_MS = 5000
|
|
11
|
+
|
|
12
|
+
let client: Client | undefined
|
|
13
|
+
let reconnectTimeout: ReturnType<typeof setTimeout> | undefined
|
|
14
|
+
let started = false
|
|
15
|
+
let connectionGeneration = 0
|
|
16
|
+
|
|
17
|
+
interface GetKeysToInvalidateArgs {
|
|
18
|
+
table: string
|
|
19
|
+
recordId: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getKeysToInvalidate({ table, recordId }: GetKeysToInvalidateArgs) {
|
|
23
|
+
// Using `toLowerCase()` rather than `toLocaleLowerCase()` because we want
|
|
24
|
+
// consistent casing across all locales.
|
|
25
|
+
const typeName = table.charAt(0).toLowerCase() + table.slice(1)
|
|
26
|
+
|
|
27
|
+
const keys = [
|
|
28
|
+
`Query.${typeName}`,
|
|
29
|
+
`Query.${pluralize(typeName)}`,
|
|
30
|
+
`${typeName}:${recordId}`,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
return keys
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function reconnect(generation: number) {
|
|
37
|
+
if (reconnectTimeout) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
reconnectTimeout = setTimeout(async () => {
|
|
42
|
+
reconnectTimeout = undefined
|
|
43
|
+
|
|
44
|
+
if (generation !== connectionGeneration) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await connect()
|
|
49
|
+
}, RECONNECT_DELAY_MS)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface NotificationPayload {
|
|
53
|
+
table: string
|
|
54
|
+
operation: string
|
|
55
|
+
recordId: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function onNotification(payload: string | undefined) {
|
|
59
|
+
if (!payload) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const parsed: NotificationPayload = JSON.parse(payload)
|
|
65
|
+
const keys = getKeysToInvalidate(parsed)
|
|
66
|
+
|
|
67
|
+
await liveQueryStore?.invalidate(keys)
|
|
68
|
+
|
|
69
|
+
logger.debug(
|
|
70
|
+
{ operation: parsed.operation, table: parsed.table, keys },
|
|
71
|
+
'Invalidated live query keys from Postgres notification'
|
|
72
|
+
)
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error(
|
|
75
|
+
{ error, payload },
|
|
76
|
+
'Failed to process Postgres notification payload'
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function connect() {
|
|
82
|
+
connectionGeneration = connectionGeneration + 1
|
|
83
|
+
const generation = connectionGeneration
|
|
84
|
+
const prismaConfigPath = getPaths().api.prismaConfig
|
|
85
|
+
const prismaConfig = await loadPrismaConfig(prismaConfigPath)
|
|
86
|
+
const prismaDatasourceUrl = prismaConfig.datasource?.url
|
|
87
|
+
|
|
88
|
+
if (!prismaDatasourceUrl) {
|
|
89
|
+
const config = JSON.stringify(prismaConfig, null, 2)
|
|
90
|
+
|
|
91
|
+
throw new Error(
|
|
92
|
+
'Could not determine Postgres connection URL from Prisma config ' +
|
|
93
|
+
`datasource. Using parsed Prisma config: ${config}`
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (reconnectTimeout) {
|
|
99
|
+
clearTimeout(reconnectTimeout)
|
|
100
|
+
reconnectTimeout = undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (client) {
|
|
104
|
+
const previousClient = client
|
|
105
|
+
client = undefined
|
|
106
|
+
previousClient.removeAllListeners()
|
|
107
|
+
await previousClient.end().catch(() => {})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const nextClient = new Client({ connectionString: prismaDatasourceUrl })
|
|
111
|
+
|
|
112
|
+
nextClient.on('notification', async (msg) => {
|
|
113
|
+
await onNotification(msg.payload)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
nextClient.on('error', (error) => {
|
|
117
|
+
logger.error(
|
|
118
|
+
{ error },
|
|
119
|
+
'Postgres live query listener encountered an error'
|
|
120
|
+
)
|
|
121
|
+
reconnect(generation)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
nextClient.on('end', () => {
|
|
125
|
+
logger.warn('Postgres live query listener disconnected')
|
|
126
|
+
reconnect(generation)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await nextClient.connect()
|
|
130
|
+
await nextClient.query(`LISTEN ${LIVE_QUERY_CHANNEL}`)
|
|
131
|
+
|
|
132
|
+
if (generation !== connectionGeneration) {
|
|
133
|
+
nextClient.removeAllListeners()
|
|
134
|
+
await nextClient.end().catch(() => {})
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
client = nextClient
|
|
139
|
+
logger.info('Postgres live query listener connected')
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error({ error }, 'Failed to connect Postgres live query listener')
|
|
142
|
+
reconnect(generation)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function startLiveQueryListener() {
|
|
147
|
+
if (started) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
started = true
|
|
152
|
+
await connect()
|
|
153
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION cedar_notify_table_change() RETURNS TRIGGER AS $$
|
|
2
|
+
DECLARE
|
|
3
|
+
record_id text;
|
|
4
|
+
BEGIN
|
|
5
|
+
IF (TG_OP = 'DELETE') THEN
|
|
6
|
+
record_id := OLD.id::text;
|
|
7
|
+
ELSE
|
|
8
|
+
record_id := NEW.id::text;
|
|
9
|
+
END IF;
|
|
10
|
+
|
|
11
|
+
PERFORM pg_notify(
|
|
12
|
+
'table_change',
|
|
13
|
+
json_build_object(
|
|
14
|
+
'table', TG_TABLE_NAME,
|
|
15
|
+
'operation', TG_OP,
|
|
16
|
+
'recordId', record_id
|
|
17
|
+
)::text
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
RETURN NULL;
|
|
21
|
+
END;
|
|
22
|
+
$$ LANGUAGE plpgsql;
|
|
23
|
+
|
|
24
|
+
CREATE OR REPLACE FUNCTION cedar_attach_notify_triggers() RETURNS void AS $$
|
|
25
|
+
DECLARE
|
|
26
|
+
table_record record;
|
|
27
|
+
BEGIN
|
|
28
|
+
FOR table_record IN
|
|
29
|
+
SELECT table_schema, table_name
|
|
30
|
+
FROM information_schema.tables
|
|
31
|
+
WHERE table_type = 'BASE TABLE'
|
|
32
|
+
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
33
|
+
AND table_name <> '_prisma_migrations'
|
|
34
|
+
LOOP
|
|
35
|
+
EXECUTE format(
|
|
36
|
+
'DROP TRIGGER IF EXISTS cedar_notify_change_trigger ON %I.%I',
|
|
37
|
+
table_record.table_schema,
|
|
38
|
+
table_record.table_name
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
EXECUTE format(
|
|
42
|
+
'CREATE TRIGGER cedar_notify_change_trigger
|
|
43
|
+
AFTER INSERT OR UPDATE OR DELETE ON %I.%I
|
|
44
|
+
FOR EACH ROW EXECUTE FUNCTION cedar_notify_table_change()',
|
|
45
|
+
table_record.table_schema,
|
|
46
|
+
table_record.table_name
|
|
47
|
+
);
|
|
48
|
+
END LOOP;
|
|
49
|
+
END;
|
|
50
|
+
$$ LANGUAGE plpgsql;
|
|
51
|
+
|
|
52
|
+
SELECT cedar_attach_notify_triggers();
|
|
53
|
+
|
|
54
|
+
CREATE OR REPLACE FUNCTION cedar_event_on_table_create() RETURNS event_trigger AS $$
|
|
55
|
+
DECLARE
|
|
56
|
+
cmd record;
|
|
57
|
+
schema_name text;
|
|
58
|
+
table_name text;
|
|
59
|
+
ident_parts text[];
|
|
60
|
+
BEGIN
|
|
61
|
+
FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP
|
|
62
|
+
IF cmd.object_type = 'table' THEN
|
|
63
|
+
schema_name := cmd.schema_name;
|
|
64
|
+
ident_parts := parse_ident(cmd.object_identity);
|
|
65
|
+
table_name := ident_parts[array_length(ident_parts, 1)];
|
|
66
|
+
|
|
67
|
+
IF schema_name IS NULL THEN
|
|
68
|
+
CONTINUE;
|
|
69
|
+
END IF;
|
|
70
|
+
|
|
71
|
+
IF schema_name IN ('pg_catalog', 'information_schema') THEN
|
|
72
|
+
CONTINUE;
|
|
73
|
+
END IF;
|
|
74
|
+
|
|
75
|
+
IF table_name = '_prisma_migrations' THEN
|
|
76
|
+
CONTINUE;
|
|
77
|
+
END IF;
|
|
78
|
+
|
|
79
|
+
EXECUTE format(
|
|
80
|
+
'DROP TRIGGER IF EXISTS cedar_notify_change_trigger ON %I.%I',
|
|
81
|
+
schema_name,
|
|
82
|
+
table_name
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
EXECUTE format(
|
|
86
|
+
'CREATE TRIGGER cedar_notify_change_trigger
|
|
87
|
+
AFTER INSERT OR UPDATE OR DELETE ON %I.%I
|
|
88
|
+
FOR EACH ROW EXECUTE FUNCTION cedar_notify_table_change()',
|
|
89
|
+
schema_name,
|
|
90
|
+
table_name
|
|
91
|
+
);
|
|
92
|
+
END IF;
|
|
93
|
+
END LOOP;
|
|
94
|
+
END;
|
|
95
|
+
$$ LANGUAGE plpgsql;
|
|
96
|
+
|
|
97
|
+
CREATE EVENT TRIGGER cedar_on_create_table
|
|
98
|
+
ON ddl_command_end
|
|
99
|
+
WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS')
|
|
100
|
+
EXECUTE FUNCTION cedar_event_on_table_create();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
|
|
1
|
+
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
2
2
|
import { getEpilogue } from "./util.js";
|
|
3
3
|
const command = "setup-inngest";
|
|
4
4
|
const description = "Setup Inngest for background, scheduled, delayed, multi-step, and fan-out jobs";
|
|
5
|
-
const EXPERIMENTAL_TOPIC_ID = 4866;
|
|
5
|
+
const EXPERIMENTAL_TOPIC_ID = "4866";
|
|
6
6
|
const builder = (yargs) => {
|
|
7
7
|
yargs.option("force", {
|
|
8
8
|
alias: "f",
|