phero
phero copied to clipboard
ParserModel not implemented yet (kind:180)
I'm defining a database middleware per the docs as follows:
// src/db/index.ts
/* eslint-disable no-var */
import { type Collection, MongoClient, type Db } from "mongodb";
import { env } from "env";
import type {
PheroParams,
PheroContext,
PheroNextFunction,
} from "@phero/server";
import type { CatalogueItem } from "src/api/products/models";
var cachedClient: MongoClient;
var cachedDb: Db;
export type DbMiddlewareContext = {
db: Db;
client: MongoClient;
collections: {
products: Collection<CatalogueItem>;
};
};
export async function databaseMiddleware(
params: PheroParams, // not used in this middleware
context: PheroContext, // not used in this middleware
next: PheroNextFunction<DbMiddlewareContext> // we'll be adding `db` to the context, to be used later on
) {
const client = cachedClient
? cachedClient
: await MongoClient.connect(env.MONGODB_URI, { appName: "catty" });
const db = cachedDb ? cachedDb : client.db(env.MONGODB_DB);
cachedClient = client;
cachedDb = db;
await next({
db,
client,
collections: {
products: db.collection("products"),
},
});
}
I add it to my Phero File like so:
// src/phero.ts
import { createService } from "@phero/server";
import * as products from "src/api/products";
import { databaseMiddleware } from "src/db";
export const productService = createService(products, {
middleware: [databaseMiddleware],
});
I use the exported DbMiddlewareContext
type as the type of the first argument in functions which expect to follow this middleware, like so:
// src/api/createProduct.ts
import type { CatalogueItemInput } from "./models";
import type { DbMiddlewareContext } from "src/db";
import { ObjectId } from "mongodb";
export async function createProduct(
context: DbMiddlewareContext,
product: CatalogueItemInput
): Promise<CatalogueItemInput & { id: string }> {
const { collections } = context;
const { products } = collections;
const withId = { _id: new ObjectId(), ...product };
const result = await products.insertOne(withId);
if (!result.acknowledged) throw "failed";
return { ...product, id: withId._id.toHexString() };
}
For some reason, I get the following error:
src/db/index.ts:15:6 - error TS-PheroError: S145: ParserModel not implemented yet:
Db
(kind:180)15 db: Db;
If you think this is a bug, please submit an issue here: https://github.com/phero-hq/phero/issues
Have I found a bug or just a limitation / missing feature?
@helmturner Your code seems great, let me get into it to figure out what’s going on 👍
Thanks! Forgot to mention I'm on version 0.9.13
.
I was able to glean a bit more information by running npx phero server build
, which gave me the following output with the error message:
node: <ref *6> NodeObject {
pos: 362,
end: 365,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 1,
parent: <ref *3> NodeObject {
pos: 356,
end: 366,
flags: 0,
modifierFlagsCache: 536870912,
transformFlags: 1,
parent: <ref *1> NodeObject {
pos: 354,
end: 454,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 1,
parent: <ref *2> NodeObject {
pos: 319,
end: 455,
flags: 0,
modifierFlagsCache: 536870913,
transformFlags: 1,
parent: SourceFileObject {
pos: 0,
end: 1059,
flags: 2048,
modifierFlagsCache: 0,
transformFlags: 4195713,
parent: undefined,
kind: 308,
statements: [Array],
endOfFileToken: [TokenObject],
fileName: '/Users/alec/dev/Catty/1/src/db/index.ts',
text: '/* eslint-disable no-var */\n' +
'import { type Collection, MongoClient, type Db } from "mongodb";\n' +
'import { env } from "env";\n' +
'import type {\n' +
' PheroParams,\n' +
' PheroContext,\n' +
' PheroNextFunction,\n' +
'} from "@phero/server";\n' +
'import type { CatalogueItem } from "@/api/products/models";\n' +
'\n' +
'var cachedClient: MongoClient;\n' +
'var cachedDb: Db;\n' +
'\n' +
'export type DbMiddlewareContext = {\n' +
' db: Db;\n' +
' client: MongoClient;\n' +
' collections: {\n' +
' products: Collection<CatalogueItem>;\n' +
' };\n' +
'};\n' +
'\n' +
'export async function databaseMiddleware(\n' +
' params: PheroParams, // not used in this middleware\n' +
' context: PheroContext, // not used in this middleware\n' +
" next: PheroNextFunction<DbMiddlewareContext> // we'll be adding `db` to the context, to be used later on\n" +
') {\n' +
' const client = cachedClient\n' +
' ? cachedClient\n' +
' : await MongoClient.connect(env.MONGODB_URI, { appName: "catty" });\n' +
' const db = cachedDb ? cachedDb : client.db(env.MONGODB_DB);\n' +
'\n' +
' cachedClient = client;\n' +
' cachedDb = db;\n' +
'\n' +
' await next({\n' +
' db,\n' +
' client,\n' +
' collections: {\n' +
' products: db.collection("products"),\n' +
' },\n' +
' });\n' +
'}\n',
languageVersion: 4,
languageVariant: 0,
scriptKind: 3,
isDeclarationFile: false,
hasNoDefaultLib: false,
bindDiagnostics: [],
bindSuggestionDiagnostics: undefined,
externalModuleIndicator: [NodeObject],
setExternalModuleIndicator: [Function: callback],
pragmas: Map(0) {},
checkJsDirective: undefined,
referencedFiles: [],
typeReferenceDirectives: [],
libReferenceDirectives: [],
amdDependencies: [],
commentDirectives: undefined,
nodeCount: 156,
identifierCount: 59,
identifiers: [Map],
parseDiagnostics: [],
path: '/users/alec/dev/catty/1/src/db/index.ts',
resolvedPath: '/users/alec/dev/catty/1/src/db/index.ts',
originalFileName: '/Users/alec/dev/Catty/1/src/db/index.ts',
packageJsonLocations: undefined,
packageJsonScope: undefined,
imports: [Array],
moduleAugmentations: [],
ambientModuleNames: [],
resolvedModules: [Object],
symbol: [SymbolObject],
locals: [Map],
nextContainer: [NodeObject],
endFlowNode: [Object],
symbolCount: 34,
classifiableNames: [Set],
lineMap: [Array]
},
kind: 262,
symbol: SymbolObject {
flags: 524288,
escapedName: 'DbMiddlewareContext',
declarations: [Array],
parent: [SymbolObject],
isReferenced: 788968,
id: 26
},
localSymbol: SymbolObject {
flags: 0,
escapedName: 'DbMiddlewareContext',
declarations: [Array],
parent: undefined,
exportSymbol: [SymbolObject]
},
locals: Map(0) {},
nextContainer: [Circular *1],
name: IdentifierObject {
pos: 332,
end: 352,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 0,
parent: [Circular *2],
kind: 79,
originalKeywordKind: undefined,
escapedText: 'DbMiddlewareContext',
flowNode: [Object]
},
modifiers: [
[TokenObject],
pos: 319,
end: 327,
hasTrailingComma: false,
transformFlags: 0
],
typeParameters: undefined,
type: [Circular *1],
illegalDecorators: undefined,
id: 1320
},
kind: 184,
members: [
[Circular *3],
NodeObject {
pos: 366,
end: 389,
flags: 0,
modifierFlagsCache: 536870912,
transformFlags: 1,
parent: [Circular *1],
kind: 168,
symbol: [SymbolObject],
localSymbol: undefined,
locals: undefined,
nextContainer: undefined,
name: [IdentifierObject],
modifiers: undefined,
type: [NodeObject],
questionToken: undefined,
initializer: undefined
},
NodeObject {
pos: 389,
end: 452,
flags: 0,
modifierFlagsCache: 536870912,
transformFlags: 1,
parent: [Circular *1],
kind: 168,
symbol: [SymbolObject],
localSymbol: undefined,
locals: undefined,
nextContainer: undefined,
name: [IdentifierObject],
modifiers: undefined,
type: [NodeObject],
questionToken: undefined,
initializer: undefined
},
pos: 356,
end: 452,
hasTrailingComma: false,
transformFlags: 1
],
symbol: SymbolObject {
flags: 2048,
escapedName: '__type',
declarations: [ [Circular *1] ],
members: Map(3) {
'db' => [SymbolObject],
'client' => [SymbolObject],
'collections' => [SymbolObject]
},
id: 27
},
nextContainer: <ref *4> NodeObject {
pos: 404,
end: 451,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 1,
parent: NodeObject {
pos: 389,
end: 452,
flags: 0,
modifierFlagsCache: 536870912,
transformFlags: 1,
parent: [Circular *1],
kind: 168,
symbol: [SymbolObject],
localSymbol: undefined,
locals: undefined,
nextContainer: undefined,
name: [IdentifierObject],
modifiers: undefined,
type: [Circular *4],
questionToken: undefined,
initializer: undefined
},
kind: 184,
members: [
[NodeObject],
pos: 406,
end: 447,
hasTrailingComma: false,
transformFlags: 1
],
symbol: SymbolObject {
flags: 2048,
escapedName: '__type',
declarations: [Array],
members: [Map]
}
},
id: 1319
},
kind: 168,
symbol: <ref *5> SymbolObject {
flags: 4,
escapedName: 'db',
declarations: [ [Circular *3] ],
valueDeclaration: [Circular *3],
parent: SymbolObject {
flags: 2048,
escapedName: '__type',
declarations: [ [NodeObject] ],
members: Map(3) {
'db' => [Circular *5],
'client' => [SymbolObject],
'collections' => [SymbolObject]
},
id: 27
}
},
localSymbol: undefined,
locals: undefined,
nextContainer: undefined,
name: IdentifierObject {
pos: 356,
end: 361,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 0,
parent: [Circular *3],
kind: 79,
originalKeywordKind: undefined,
escapedText: 'db',
flowNode: { flags: 2 }
},
modifiers: undefined,
type: [Circular *6],
questionToken: undefined,
initializer: undefined
},
kind: 180,
typeName: IdentifierObject {
pos: 362,
end: 365,
flags: 0,
modifierFlagsCache: 0,
transformFlags: 0,
parent: [Circular *6],
kind: 79,
originalKeywordKind: undefined,
escapedText: 'Db',
flowNode: { flags: 2 }
},
typeArguments: undefined,
id: 1321
}
}
Thanks for the extra information. The issue seems to have something to do with the fact that we’re validating (runtime) what data flows through all middleware and into your function, and we don’t support everything that is inside of the Mongo API.
I couldn’t get to a nice workaround immediately, but will get to that soon. Could you maybe expose the Mongo client from a singleton, instead of passing it along with middleware? I can see that your approach is how you’d want it, but that could be a workaround for now.
The reason this doesn't work is because we cannot generate the parsers used for runtime validation (yet?), like @Jpunt said.
You can still use this setup with a workaround by setting the type of the client to unknown
in the middleware. You'll have to cast it to the MongoClient
and Db
type inside your function tho, which is far from ideal but does the job:
// src/db/index.ts
import type {
PheroParams,
PheroContext,
PheroNextFunction,
} from "@phero/server"
import type { CatalogueItem } from "src/api/products/models"
let cachedClient: MongoClient
let cachedDb: Db
export type DbMiddlewareContext = {
db: unknown
client: unknown
collections: {
products: Collection<CatalogueItem>
}
}
export async function databaseMiddleware(
params: PheroParams,
context: PheroContext,
next: PheroNextFunction<DbMiddlewareContext>,
) {
const client = cachedClient
? cachedClient
: await MongoClient.connect(env.MONGODB_URI, { appName: "catty" })
const db = cachedDb ? cachedDb : client.db(env.MONGODB_DB)
cachedClient = client
cachedDb = db
await next({
db,
client,
collections: {
products: db.collection("products"),
},
})
}
// src/api/createProduct.ts
import type { CatalogueItemInput } from "./models"
import type { DbMiddlewareContext } from "src/db"
export async function createProduct(
context: DbMiddlewareContext,
product: CatalogueItemInput,
): Promise<CatalogueItemInput & { id: string }> {
const db = context.db as Db
const client = context.client as MongoClient
}
Please note that I just tested this with a Prisma setup and not mongo, but it should be similar.
Thanks for the reply! Both of those work-arounds would be fine. I was mostly kicking the tires, anyhow 😋
I imagine it's a tall order to implement parsers for every edge case... if it's even possible, given the limits of static analysis.
I think I'd be quite content with an "escape hatch" that allows for opting out of parsing for certain types - Perhaps a "no-phero" file where you can drop no-parse type defs, or a NoPhero "magic" union type exported from the phero-file (my brain keeps trying to spell it phero-phile 🙄).
On Sun, Feb 5, 2023, 06:26 Jim @.***> wrote:
The reason this doesn't work is because we cannot generate the parsers used for runtime validation (yet?), like @Jpunt https://github.com/Jpunt said.
You can still use this setup with a workaround by setting the type of the client to unknown in the middleware. You'll have to cast it to the MongoClient and Db type inside your function tho, which is far from ideal but does the job:
// src/db/index.ts import type { PheroParams, PheroContext, PheroNextFunction, } from @.***/server" import type { CatalogueItem } from "src/api/products/models"
let cachedClient: MongoClient let cachedDb: Db
export type DbMiddlewareContext = { db: unknown client: unknown collections: { products: Collection<CatalogueItem> } }
export async function databaseMiddleware( params: PheroParams, context: PheroContext, next: PheroNextFunction<DbMiddlewareContext>, ) { const client = cachedClient ? cachedClient : await MongoClient.connect(env.MONGODB_URI, { appName: "catty" }) const db = cachedDb ? cachedDb : client.db(env.MONGODB_DB)
cachedClient = client cachedDb = db
await next({ db, client, collections: { products: db.collection("products"), }, }) }
// src/api/createProduct.ts import type { CatalogueItemInput } from "./models" import type { DbMiddlewareContext } from "src/db"
export async function createProduct( context: DbMiddlewareContext, product: CatalogueItemInput, ): Promise<CatalogueItemInput & { id: string }> { const db = context.db as Db const client = context.client as MongoClient }
Please note that I just tested this with a Prisma setup and not mongo, but it should be similar.
— Reply to this email directly, view it on GitHub https://github.com/phero-hq/phero/issues/100#issuecomment-1417688848, or unsubscribe https://github.com/notifications/unsubscribe-auth/APZV5NIWG3PSGQCHN4TDOEDWV6L5PANCNFSM6AAAAAAUII6DRQ . You are receiving this because you were mentioned.Message ID: @.***>
my brain keeps trying to spell it phero-phile 🙄
Yeah, we got that too 😅 Any suggestions? Phero-entry/declaration/code/?
3 & 1/2 months later... how about "Phero module" or "Phero spec" or "Phero def"? The latter is reminiscent of "Type def", which seems fitting for the nature of the lib.